@iyulab/components 1.7.2 → 1.8.1

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 (58) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/dist/components/icon/UIcon.js +5 -7
  3. package/dist/components/input/UInput.js +2 -0
  4. package/dist/components/option/UOption.d.ts +2 -0
  5. package/dist/components/option/UOption.js +9 -0
  6. package/dist/components/radio/URadio.js +5 -1
  7. package/dist/components/rating/URating.js +6 -1
  8. package/dist/components/select/USelect.d.ts +4 -0
  9. package/dist/components/select/USelect.js +14 -1
  10. package/dist/components/slider/USlider.js +8 -0
  11. package/dist/components/textarea/UTextarea.js +2 -0
  12. package/dist/react/UAlert.d.ts +3 -1
  13. package/dist/react/UAvatar.d.ts +3 -1
  14. package/dist/react/UBadge.d.ts +3 -1
  15. package/dist/react/UBreadcrumb.d.ts +3 -1
  16. package/dist/react/UBreadcrumbItem.d.ts +3 -1
  17. package/dist/react/UButton.d.ts +3 -1
  18. package/dist/react/UButtonGroup.d.ts +3 -1
  19. package/dist/react/UCard.d.ts +3 -1
  20. package/dist/react/UCarousel.d.ts +3 -1
  21. package/dist/react/UCheckbox.d.ts +3 -1
  22. package/dist/react/UChip.d.ts +3 -1
  23. package/dist/react/UCopyButton.d.ts +3 -1
  24. package/dist/react/UDialog.d.ts +3 -1
  25. package/dist/react/UDivider.d.ts +3 -1
  26. package/dist/react/UDrawer.d.ts +3 -1
  27. package/dist/react/UField.d.ts +3 -1
  28. package/dist/react/UForm.d.ts +3 -1
  29. package/dist/react/UIcon.d.ts +3 -1
  30. package/dist/react/UIconButton.d.ts +3 -1
  31. package/dist/react/UInput.d.ts +3 -1
  32. package/dist/react/UMenu.d.ts +3 -1
  33. package/dist/react/UMenuItem.d.ts +3 -1
  34. package/dist/react/UOption.d.ts +3 -1
  35. package/dist/react/UPanel.d.ts +3 -1
  36. package/dist/react/UPopover.d.ts +3 -1
  37. package/dist/react/UProgressBar.d.ts +3 -1
  38. package/dist/react/UProgressRing.d.ts +3 -1
  39. package/dist/react/URadio.d.ts +3 -1
  40. package/dist/react/URating.d.ts +3 -1
  41. package/dist/react/USelect.d.ts +3 -1
  42. package/dist/react/USkeleton.d.ts +3 -1
  43. package/dist/react/USlider.d.ts +3 -1
  44. package/dist/react/USpinner.d.ts +3 -1
  45. package/dist/react/USplitPanel.d.ts +3 -1
  46. package/dist/react/USwitch.d.ts +3 -1
  47. package/dist/react/UTab.d.ts +3 -1
  48. package/dist/react/UTabPanel.d.ts +3 -1
  49. package/dist/react/UTag.d.ts +3 -1
  50. package/dist/react/UTextarea.d.ts +3 -1
  51. package/dist/react/UTooltip.d.ts +3 -1
  52. package/dist/react/UTree.d.ts +3 -1
  53. package/dist/react/UTreeItem.d.ts +3 -1
  54. package/dist/utilities/icons.d.ts +38 -4
  55. package/dist/utilities/icons.js +83 -51
  56. package/package.json +1 -1
  57. package/plugins/vite-plugin-react-wrapper.js +12 -1
  58. package/skills/iyulab-components/references/utilities/icons.md +14 -7
package/CHANGELOG.md CHANGED
@@ -1,5 +1,29 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.8.1] - 2026-07-24
4
+
5
+ ### Fixed
6
+ - **u-field 합성 폼 컨트롤이 접근성 트리에서 이름 없이 노출되던 문제 수정** (WCAG 1.3.1·4.1.2 — U-Platform T8-1 Playwright E2E 실측, ISSUE-20260723-uinput-label-a11y). 라벨은 `u-field` 의 별도 섀도 스코프에 렌더되어 `label[for]` 로 컨트롤과 연결될 수 없었고, 섀도 경계 탓에 cross-root `aria-labelledby` 도 현 브라우저에서 신뢰성 있게 배송되지 않는다. 이제 각 컴포넌트가 자신의 접근 이름 호스트에 라벨을 `aria-label`(설명은 `aria-description`)로 미러링한다 — `u-input`/`u-textarea`(네이티브 컨트롤), `u-rating`/`u-radio`(`role=radiogroup`), `u-select`(`role=combobox` + `aria-expanded`/`aria-haspopup`/`aria-controls`), `u-slider`(`role=slider` + `aria-valuenow`/`valuemin`/`valuemax`/`valuetext`). 아울러 `u-option` 이 사용 맥락에 맞는 자식 역할·상태를 노출한다 — radiogroup(`marker='radio'`) 안에서는 `role=radio` + `aria-checked`, listbox(`u-select`/`u-input` combobox) 에서는 `role=option` + `aria-selected`(+ `aria-disabled`) — 이름만 있고 비어 보이던 radiogroup/combobox 위젯이 자식까지 정합하게 읽힌다. `u-rating` 심볼(`role=radio`)도 커밋 값 기준 `aria-checked` 를 노출한다. `getByLabel`/`getByRole({name})` 로케이터와 스크린리더가 컨트롤을 이름으로 인식한다.
7
+ - **React 래퍼 `.d.ts` 타이핑 2건 수정** (React 19 + TS strict 컴파일 차단 — U-CMMS 실측, ISSUE-20260722-react-wrapper-typing). (1) `React.HTMLAttributes` 의 이벤트 핸들러(`onChange` 등)와 래퍼의 CustomEvent 시그니처가 교집합되어 어떤 핸들러도 대입 불가였던 것을, HTMLAttributes 쪽 동명 이벤트 키를 `Omit` 해 CustomEvent 시그니처만 남기도록 교정. (2) `Partial<Element>` 의 DOM `children: HTMLCollection` 이 JSX children 을 가려 `<UButton>text</UButton>` 이 TS2747 로 실패하던 것을, `Omit<Partial<Element>, keyof HTMLAttributes>` 로 DOM 전용 키를 제거하고 React 친화 타입(`children: ReactNode`)이 HTMLAttributes 에서 공급되도록 교정. 소비자의 `as unknown as ComponentType` 우회 제거 가능.
8
+
9
+ ## [1.8.0] - 2026-07-22
10
+
11
+ ### Fixed
12
+ - **`u-icon` 아이콘 리졸브가 재렌더·재마운트마다 다시 fetch되던 스톰 수정** — `IconRegistry.resolve()`가 리졸버 결과를 캐시하지 않아, SSE 스트리밍처럼 같은 아이콘이 반복 재마운트되는 UI에서 단일 아이콘에 수백 회 fetch가 발생했다(미존재 아이콘은 404 스톰으로 콘솔 오염 + dev 서버 부하 — SMI.AIMS 실측, ISSUE-components-20260722-iconregistry-resolver-no-cache). 이제 레지스트리가 (lib, name) 단위 캐싱과 동시 요청 in-flight dedupe를 소유해, 커스텀 리졸버를 포함한 모든 라이브러리에서 아이콘당 세션 1회만 리졸브된다. `u-icon`의 `src` 경로·무-lib 기본(baseUrl) 경로도 신설 `IconRegistry.resolveUrl(url)`을 경유해 동일하게 캐시된다.
13
+ - 내장 CDN 리졸버(tabler/heroicons/lucide/bootstrap)가 네트워크 오류를 `undefined`로 삼키던 것을 throw 전파로 교정 — 일시 장애가 세션 내 not-found로 오인·고착되지 않고 다음 조회에서 재시도된다.
14
+
15
+ ### Changed
16
+ - **`IconResolver` 계약 명확화 (동작 변경)** — 리졸버의 `undefined` 반환은 이제 **not-found 확정**을 뜻하며 네거티브 캐시되어 세션 내 재호출되지 않는다(탈출구: `IconCache.clear()`). **일시 오류(네트워크 장애 등)에는 `undefined` 대신 `throw`를 사용할 것** — throw는 캐시되지 않아 다음 조회 시 재시도된다. 일시 실패에 `undefined`를 반환하던 커스텀 리졸버는 throw로 전환해야 재시도 동작을 유지한다.
17
+ - `IconRegistry.unregister(lib)`가 해당 라이브러리의 캐시 항목을 함께 비운다 — 내장 CDN 라이브러리를 로컬 리졸버로 교체(`unregister` → `register`, 폐쇄망 대응)할 때 이전 리졸버의 stale 결과가 남지 않는다.
18
+
19
+ ### Added
20
+ - `IconRegistry.resolveUrl(url)` — URL 직접 리졸브(캐시 + dedupe). 예약 네임스페이스 `url`로 `IconCache`에 저장되며 소비앱의 prewarm 용도로도 사용 가능.
21
+ - `IconCache.clear(lib)` — 특정 라이브러리 항목만 클리어(전체 클리어는 기존대로 인자 없이).
22
+ - `IconCache.set()`이 `undefined`(네거티브 항목) 저장을 허용.
23
+
24
+ ### Documentation
25
+ - `docs/icons.md`·skills 레퍼런스에 리졸버 계약(성공/not-found/일시 오류)·캐싱 동작·내장 라이브러리 오버라이드 절차(`unregister`→`register`) 문서화. CDN 버전 표기 드리프트 교정(lucide 0.577.0, bootstrap 1.13.1).
26
+
3
27
  ## [1.7.2] - 2026-07-19
4
28
 
5
29
  ### Fixed
@@ -13,8 +13,9 @@ var UIcon = class UIcon extends UElement {
13
13
  this.styles = [super.styles, styles];
14
14
  }
15
15
  render() {
16
- if (this.src) return until(fetch(this.src).then((r) => r.text()).then((html) => {
17
- return html ? unsafeHTML(this.sanitize(html)) : nothing;
16
+ if (this.src) return until(IconRegistry.resolveUrl(this.src).then((html) => {
17
+ const sanitized = this.sanitize(html);
18
+ return sanitized ? unsafeHTML(sanitized) : nothing;
18
19
  }), nothing);
19
20
  if (this.name) return until(this.resolve(this.name).then((html) => {
20
21
  return html ? unsafeHTML(html) : nothing;
@@ -24,12 +25,9 @@ var UIcon = class UIcon extends UElement {
24
25
  async resolve(name) {
25
26
  let html = void 0;
26
27
  if (this.lib) html = await IconRegistry.resolve(this.lib, name);
27
- else try {
28
+ else {
28
29
  const url = `${getDefaultBaseUrl().replace(/\/$/, "")}/${name}.svg`;
29
- const response = await fetch(url);
30
- if (response.ok) html = await response.text();
31
- } catch (error) {
32
- console.error(error);
30
+ html = await IconRegistry.resolveUrl(url);
33
31
  }
34
32
  return this.sanitize(html);
35
33
  }
@@ -148,6 +148,8 @@ var UInput = class UInput extends UFormControlElement {
148
148
 
149
149
  <input part="input"
150
150
  type=${this.type === "password" && this.showPassword ? "text" : this.type}
151
+ aria-label=${ifDefined(this.label)}
152
+ aria-description=${ifDefined(this.description)}
151
153
  name=${ifDefined(this.name)}
152
154
  ?required=${this.required}
153
155
  ?disabled=${this.disabled}
@@ -1,3 +1,4 @@
1
+ import { PropertyValues } from 'lit';
1
2
  import { UElement } from '../UElement.js';
2
3
  export type OptionMarker = 'radio' | 'check';
3
4
  /**
@@ -31,6 +32,7 @@ export declare class UOption extends UElement {
31
32
  /** 옵션 고유값 */
32
33
  value: string;
33
34
  connectedCallback(): void;
35
+ protected updated(changed: PropertyValues): void;
34
36
  render(): import('lit-html').TemplateResult<1>;
35
37
  private renderMarker;
36
38
  /** 라벨(기본 slot)의 텍스트 노드만 추출한다 — 엘리먼트 자식(예: 설명용 `<p>`)은 제외. */
@@ -21,6 +21,15 @@ var UOption = class UOption extends UElement {
21
21
  this.setAttribute("role", "option");
22
22
  this.setAttribute("tabindex", this.disabled ? "-1" : "0");
23
23
  }
24
+ updated(changed) {
25
+ super.updated(changed);
26
+ const isRadio = this.marker === "radio";
27
+ this.setAttribute("role", isRadio ? "radio" : "option");
28
+ this.removeAttribute(isRadio ? "aria-selected" : "aria-checked");
29
+ this.setAttribute(isRadio ? "aria-checked" : "aria-selected", String(this.selected));
30
+ this.setAttribute("aria-disabled", String(this.disabled));
31
+ this.setAttribute("tabindex", this.disabled ? "-1" : "0");
32
+ }
24
33
  render() {
25
34
  return html`
26
35
  ${this.renderMarker()}
@@ -7,6 +7,7 @@ import { UOption } from "../option/UOption.js";
7
7
  import { styles } from "./URadio.styles.js";
8
8
  import { html } from "lit";
9
9
  import { customElement, property, state } from "lit/decorators.js";
10
+ import { ifDefined } from "lit/directives/if-defined.js";
10
11
  //#region src/components/radio/URadio.ts
11
12
  var URadio = class URadio extends UFormControlElement {
12
13
  constructor(..._args) {
@@ -91,7 +92,10 @@ var URadio = class URadio extends UFormControlElement {
91
92
  .description=${this.description}
92
93
  .validationMessage=${this.validationMessage}
93
94
  >
94
- <div class="container" part="container">
95
+ <div class="container" part="container"
96
+ role="radiogroup"
97
+ aria-label=${ifDefined(this.label)}
98
+ aria-description=${ifDefined(this.description)}>
95
99
  <slot @slotchange=${this.handleSlotChange}></slot>
96
100
  </div>
97
101
  </u-field>
@@ -7,6 +7,7 @@ import "../field/UField.js";
7
7
  import { styles } from "./URating.styles.js";
8
8
  import { html } from "lit";
9
9
  import { customElement, property, state } from "lit/decorators.js";
10
+ import { ifDefined } from "lit/directives/if-defined.js";
10
11
  //#region src/components/rating/URating.ts
11
12
  var URating = class URating extends UFormControlElement {
12
13
  constructor(..._args) {
@@ -110,13 +111,17 @@ var URating = class URating extends UFormControlElement {
110
111
  .description=${this.description}
111
112
  .validationMessage=${this.validationMessage}
112
113
  >
113
- <div class="symbols" part="container" role="radiogroup">
114
+ <div class="symbols" part="container" role="radiogroup"
115
+ aria-label=${ifDefined(this.label)}
116
+ aria-description=${ifDefined(this.description)}>
114
117
  ${Array.from({ length: this.max }, (_, i) => {
115
118
  const score = i + 1;
116
119
  const fill = Math.max(0, Math.min(1, value - score + 1));
117
120
  return html`
118
121
  <span class="symbol" part="symbol"
119
122
  role="radio"
123
+ aria-checked=${score === Math.round(this.value || 0) ? "true" : "false"}
124
+ aria-disabled=${this.interactive ? "false" : "true"}
120
125
  tabindex=${this.interactive ? "0" : "-1"}
121
126
  data-score=${score}
122
127
  @pointermove=${this.handleSymbolPointerMove}
@@ -48,6 +48,10 @@ export declare class USelect extends UFormControlElement<string | string[]> {
48
48
  containerEl?: HTMLElement;
49
49
  popoverEl?: UPopover;
50
50
  private options;
51
+ /** aria-expanded 배선용 팝오버 열림 상태 */
52
+ private open;
53
+ /** combobox → listbox 연결용 고유 id (aria-controls) */
54
+ private readonly listboxId;
51
55
  protected shouldValidate(changed: PropertyValues): boolean;
52
56
  private get valueAsString();
53
57
  private get valueAsArray();
@@ -10,6 +10,7 @@ import { UPopover } from "../popover/UPopover.js";
10
10
  import { styles } from "./USelect.styles.js";
11
11
  import { html } from "lit";
12
12
  import { customElement, property, query, state } from "lit/decorators.js";
13
+ import { ifDefined } from "lit/directives/if-defined.js";
13
14
  //#region src/components/select/USelect.ts
14
15
  var USelect = class USelect extends UFormControlElement {
15
16
  constructor(..._args) {
@@ -20,6 +21,8 @@ var USelect = class USelect extends UFormControlElement {
20
21
  this.clearable = false;
21
22
  this.loading = false;
22
23
  this.options = [];
24
+ this.open = false;
25
+ this.listboxId = `u-select-listbox-${Math.random().toString(36).slice(2, 8)}`;
23
26
  this.handleSlotChange = (e) => {
24
27
  this.cleanup(this.options);
25
28
  const slot = e.target;
@@ -158,7 +161,13 @@ var USelect = class USelect extends UFormControlElement {
158
161
  ${this.valueAsArray.length} / ${this.maxCount}
159
162
  </span>
160
163
 
161
- <div class="container" part="container" tabindex=${this.disabled ? "-1" : "0"}>
164
+ <div class="container" part="container" tabindex=${this.disabled ? "-1" : "0"}
165
+ role="combobox"
166
+ aria-label=${ifDefined(this.label)}
167
+ aria-description=${ifDefined(this.description)}
168
+ aria-haspopup="listbox"
169
+ aria-expanded=${this.open}
170
+ aria-controls=${this.listboxId}>
162
171
  <slot name="prefix"></slot>
163
172
  <slot name="display">${this.renderContent()}</slot>
164
173
  <slot name="suffix"></slot>
@@ -181,11 +190,14 @@ var USelect = class USelect extends UFormControlElement {
181
190
  </u-field>
182
191
 
183
192
  <u-popover part="popover"
193
+ id=${this.listboxId}
184
194
  role="listbox"
185
195
  scrollable
186
196
  autofocus
187
197
  for=".container"
188
198
  trigger="click"
199
+ @show=${() => this.open = true}
200
+ @hide=${() => this.open = false}
189
201
  strategy="fixed"
190
202
  placement="bottom-start"
191
203
  offset="1"
@@ -322,6 +334,7 @@ __decorate([property({ converter: { fromAttribute: (value) => {
322
334
  __decorate([query(".container", true), __decorateMetadata("design:type", typeof HTMLElement === "undefined" ? Object : HTMLElement)], USelect.prototype, "containerEl", void 0);
323
335
  __decorate([query("u-popover", true), __decorateMetadata("design:type", typeof UPopover === "undefined" ? Object : UPopover)], USelect.prototype, "popoverEl", void 0);
324
336
  __decorate([state(), __decorateMetadata("design:type", Array)], USelect.prototype, "options", void 0);
337
+ __decorate([state(), __decorateMetadata("design:type", Boolean)], USelect.prototype, "open", void 0);
325
338
  USelect = __decorate([customElement("u-select")], USelect);
326
339
  //#endregion
327
340
  export { USelect };
@@ -7,6 +7,7 @@ import "../field/UField.js";
7
7
  import { styles } from "./USlider.styles.js";
8
8
  import { html } from "lit";
9
9
  import { customElement, property, query, state } from "lit/decorators.js";
10
+ import { ifDefined } from "lit/directives/if-defined.js";
10
11
  //#region src/components/slider/USlider.ts
11
12
  var USlider = class USlider extends UFormControlElement {
12
13
  constructor(..._args) {
@@ -217,6 +218,13 @@ var USlider = class USlider extends UFormControlElement {
217
218
  return html`
218
219
  <div class="thumb" part=${isEnd ? "thumb-end" : "thumb"}
219
220
  role="slider"
221
+ aria-label=${ifDefined(this.label)}
222
+ aria-valuenow=${val}
223
+ aria-valuemin=${this.min}
224
+ aria-valuemax=${this.max}
225
+ aria-valuetext=${this.formatValue(val)}
226
+ aria-orientation="horizontal"
227
+ aria-disabled=${this.disabled ? "true" : "false"}
220
228
  ?hidden=${isEnd && !this.range}
221
229
  style="left: ${pct}%"
222
230
  tabindex=${this.disabled || isEnd && !this.range ? -1 : 0}
@@ -56,6 +56,8 @@ var UTextarea = class UTextarea extends UFormControlElement {
56
56
  <div class="container" part="container">
57
57
  <textarea part="textarea"
58
58
  scrollable
59
+ aria-label=${ifDefined(this.label)}
60
+ aria-description=${ifDefined(this.description)}
59
61
  ?required=${this.required}
60
62
  ?disabled=${this.disabled}
61
63
  ?readonly=${this.readonly}
@@ -4,7 +4,9 @@ import { type ShowEventDetail } from '../events/ShowEvent';
4
4
  import { type HideEventDetail } from '../events/HideEvent';
5
5
 
6
6
  export declare const UAlert: React.ForwardRefExoticComponent<
7
- Partial<UAlertElement> & React.HTMLAttributes<UAlertElement> & {
7
+ Omit<Partial<UAlertElement>, keyof React.HTMLAttributes<UAlertElement>>
8
+ & Omit<React.HTMLAttributes<UAlertElement>, 'onShow' | 'onHide'>
9
+ & {
8
10
  onShow?: (event: CustomEvent<ShowEventDetail>) => void;
9
11
  onHide?: (event: CustomEvent<HideEventDetail>) => void;
10
12
  }
@@ -2,7 +2,9 @@ import React from 'react';
2
2
  import { UAvatar as UAvatarElement } from '../components/avatar/UAvatar';
3
3
 
4
4
  export declare const UAvatar: React.ForwardRefExoticComponent<
5
- Partial<UAvatarElement> & React.HTMLAttributes<UAvatarElement> & {
5
+ Omit<Partial<UAvatarElement>, keyof React.HTMLAttributes<UAvatarElement>>
6
+ & Omit<React.HTMLAttributes<UAvatarElement>, never>
7
+ & {
6
8
  }
7
9
  >;
8
10
 
@@ -2,7 +2,9 @@ import React from 'react';
2
2
  import { UBadge as UBadgeElement } from '../components/badge/UBadge';
3
3
 
4
4
  export declare const UBadge: React.ForwardRefExoticComponent<
5
- Partial<UBadgeElement> & React.HTMLAttributes<UBadgeElement> & {
5
+ Omit<Partial<UBadgeElement>, keyof React.HTMLAttributes<UBadgeElement>>
6
+ & Omit<React.HTMLAttributes<UBadgeElement>, never>
7
+ & {
6
8
  }
7
9
  >;
8
10
 
@@ -2,7 +2,9 @@ import React from 'react';
2
2
  import { UBreadcrumb as UBreadcrumbElement } from '../components/breadcrumb/UBreadcrumb';
3
3
 
4
4
  export declare const UBreadcrumb: React.ForwardRefExoticComponent<
5
- Partial<UBreadcrumbElement> & React.HTMLAttributes<UBreadcrumbElement> & {
5
+ Omit<Partial<UBreadcrumbElement>, keyof React.HTMLAttributes<UBreadcrumbElement>>
6
+ & Omit<React.HTMLAttributes<UBreadcrumbElement>, never>
7
+ & {
6
8
  }
7
9
  >;
8
10
 
@@ -3,7 +3,9 @@ import { UBreadcrumbItem as UBreadcrumbItemElement } from '../components/breadcr
3
3
  import { type NavigateEventDetail } from '../events/NavigateEvent';
4
4
 
5
5
  export declare const UBreadcrumbItem: React.ForwardRefExoticComponent<
6
- Partial<UBreadcrumbItemElement> & React.HTMLAttributes<UBreadcrumbItemElement> & {
6
+ Omit<Partial<UBreadcrumbItemElement>, keyof React.HTMLAttributes<UBreadcrumbItemElement>>
7
+ & Omit<React.HTMLAttributes<UBreadcrumbItemElement>, 'onNavigate'>
8
+ & {
7
9
  onNavigate?: (event: CustomEvent<NavigateEventDetail>) => void;
8
10
  }
9
11
  >;
@@ -2,7 +2,9 @@ import React from 'react';
2
2
  import { UButton as UButtonElement } from '../components/button/UButton';
3
3
 
4
4
  export declare const UButton: React.ForwardRefExoticComponent<
5
- Partial<UButtonElement> & React.HTMLAttributes<UButtonElement> & {
5
+ Omit<Partial<UButtonElement>, keyof React.HTMLAttributes<UButtonElement>>
6
+ & Omit<React.HTMLAttributes<UButtonElement>, never>
7
+ & {
6
8
  }
7
9
  >;
8
10
 
@@ -2,7 +2,9 @@ import React from 'react';
2
2
  import { UButtonGroup as UButtonGroupElement } from '../components/button-group/UButtonGroup';
3
3
 
4
4
  export declare const UButtonGroup: React.ForwardRefExoticComponent<
5
- Partial<UButtonGroupElement> & React.HTMLAttributes<UButtonGroupElement> & {
5
+ Omit<Partial<UButtonGroupElement>, keyof React.HTMLAttributes<UButtonGroupElement>>
6
+ & Omit<React.HTMLAttributes<UButtonGroupElement>, never>
7
+ & {
6
8
  }
7
9
  >;
8
10
 
@@ -2,7 +2,9 @@ import React from 'react';
2
2
  import { UCard as UCardElement } from '../components/card/UCard';
3
3
 
4
4
  export declare const UCard: React.ForwardRefExoticComponent<
5
- Partial<UCardElement> & React.HTMLAttributes<UCardElement> & {
5
+ Omit<Partial<UCardElement>, keyof React.HTMLAttributes<UCardElement>>
6
+ & Omit<React.HTMLAttributes<UCardElement>, never>
7
+ & {
6
8
  }
7
9
  >;
8
10
 
@@ -2,7 +2,9 @@ import React from 'react';
2
2
  import { UCarousel as UCarouselElement } from '../components/carousel/UCarousel';
3
3
 
4
4
  export declare const UCarousel: React.ForwardRefExoticComponent<
5
- Partial<UCarouselElement> & React.HTMLAttributes<UCarouselElement> & {
5
+ Omit<Partial<UCarouselElement>, keyof React.HTMLAttributes<UCarouselElement>>
6
+ & Omit<React.HTMLAttributes<UCarouselElement>, never>
7
+ & {
6
8
  }
7
9
  >;
8
10
 
@@ -2,7 +2,9 @@ import React from 'react';
2
2
  import { UCheckbox as UCheckboxElement } from '../components/checkbox/UCheckbox';
3
3
 
4
4
  export declare const UCheckbox: React.ForwardRefExoticComponent<
5
- Partial<UCheckboxElement> & React.HTMLAttributes<UCheckboxElement> & {
5
+ Omit<Partial<UCheckboxElement>, keyof React.HTMLAttributes<UCheckboxElement>>
6
+ & Omit<React.HTMLAttributes<UCheckboxElement>, 'onChange'>
7
+ & {
6
8
  onChange?: (event: CustomEvent) => void;
7
9
  }
8
10
  >;
@@ -4,7 +4,9 @@ import { type PickEventDetail } from '../events/PickEvent';
4
4
  import { type RemoveEventDetail } from '../events/RemoveEvent';
5
5
 
6
6
  export declare const UChip: React.ForwardRefExoticComponent<
7
- Partial<UChipElement> & React.HTMLAttributes<UChipElement> & {
7
+ Omit<Partial<UChipElement>, keyof React.HTMLAttributes<UChipElement>>
8
+ & Omit<React.HTMLAttributes<UChipElement>, 'onPick' | 'onRemove'>
9
+ & {
8
10
  onPick?: (event: CustomEvent<PickEventDetail>) => void;
9
11
  onRemove?: (event: CustomEvent<RemoveEventDetail>) => void;
10
12
  }
@@ -2,7 +2,9 @@ import React from 'react';
2
2
  import { UCopyButton as UCopyButtonElement } from '../components/copy-button/UCopyButton';
3
3
 
4
4
  export declare const UCopyButton: React.ForwardRefExoticComponent<
5
- Partial<UCopyButtonElement> & React.HTMLAttributes<UCopyButtonElement> & {
5
+ Omit<Partial<UCopyButtonElement>, keyof React.HTMLAttributes<UCopyButtonElement>>
6
+ & Omit<React.HTMLAttributes<UCopyButtonElement>, 'onCopy'>
7
+ & {
6
8
  onCopy?: (event: CustomEvent) => void;
7
9
  }
8
10
  >;
@@ -2,7 +2,9 @@ import React from 'react';
2
2
  import { UDialog as UDialogElement } from '../components/dialog/UDialog';
3
3
 
4
4
  export declare const UDialog: React.ForwardRefExoticComponent<
5
- Partial<UDialogElement> & React.HTMLAttributes<UDialogElement> & {
5
+ Omit<Partial<UDialogElement>, keyof React.HTMLAttributes<UDialogElement>>
6
+ & Omit<React.HTMLAttributes<UDialogElement>, never>
7
+ & {
6
8
  }
7
9
  >;
8
10
 
@@ -2,7 +2,9 @@ import React from 'react';
2
2
  import { UDivider as UDividerElement } from '../components/divider/UDivider';
3
3
 
4
4
  export declare const UDivider: React.ForwardRefExoticComponent<
5
- Partial<UDividerElement> & React.HTMLAttributes<UDividerElement> & {
5
+ Omit<Partial<UDividerElement>, keyof React.HTMLAttributes<UDividerElement>>
6
+ & Omit<React.HTMLAttributes<UDividerElement>, never>
7
+ & {
6
8
  }
7
9
  >;
8
10
 
@@ -2,7 +2,9 @@ import React from 'react';
2
2
  import { UDrawer as UDrawerElement } from '../components/drawer/UDrawer';
3
3
 
4
4
  export declare const UDrawer: React.ForwardRefExoticComponent<
5
- Partial<UDrawerElement> & React.HTMLAttributes<UDrawerElement> & {
5
+ Omit<Partial<UDrawerElement>, keyof React.HTMLAttributes<UDrawerElement>>
6
+ & Omit<React.HTMLAttributes<UDrawerElement>, never>
7
+ & {
6
8
  }
7
9
  >;
8
10
 
@@ -2,7 +2,9 @@ import React from 'react';
2
2
  import { UField as UFieldElement } from '../components/field/UField';
3
3
 
4
4
  export declare const UField: React.ForwardRefExoticComponent<
5
- Partial<UFieldElement> & React.HTMLAttributes<UFieldElement> & {
5
+ Omit<Partial<UFieldElement>, keyof React.HTMLAttributes<UFieldElement>>
6
+ & Omit<React.HTMLAttributes<UFieldElement>, never>
7
+ & {
6
8
  }
7
9
  >;
8
10
 
@@ -2,7 +2,9 @@ import React from 'react';
2
2
  import { UForm as UFormElement } from '../components/form/UForm';
3
3
 
4
4
  export declare const UForm: React.ForwardRefExoticComponent<
5
- Partial<UFormElement> & React.HTMLAttributes<UFormElement> & {
5
+ Omit<Partial<UFormElement>, keyof React.HTMLAttributes<UFormElement>>
6
+ & Omit<React.HTMLAttributes<UFormElement>, 'onChange'>
7
+ & {
6
8
  onChange?: (event: CustomEvent) => void;
7
9
  }
8
10
  >;
@@ -2,7 +2,9 @@ import React from 'react';
2
2
  import { UIcon as UIconElement } from '../components/icon/UIcon';
3
3
 
4
4
  export declare const UIcon: React.ForwardRefExoticComponent<
5
- Partial<UIconElement> & React.HTMLAttributes<UIconElement> & {
5
+ Omit<Partial<UIconElement>, keyof React.HTMLAttributes<UIconElement>>
6
+ & Omit<React.HTMLAttributes<UIconElement>, never>
7
+ & {
6
8
  }
7
9
  >;
8
10
 
@@ -2,7 +2,9 @@ import React from 'react';
2
2
  import { UIconButton as UIconButtonElement } from '../components/icon-button/UIconButton';
3
3
 
4
4
  export declare const UIconButton: React.ForwardRefExoticComponent<
5
- Partial<UIconButtonElement> & React.HTMLAttributes<UIconButtonElement> & {
5
+ Omit<Partial<UIconButtonElement>, keyof React.HTMLAttributes<UIconButtonElement>>
6
+ & Omit<React.HTMLAttributes<UIconButtonElement>, never>
7
+ & {
6
8
  }
7
9
  >;
8
10
 
@@ -2,7 +2,9 @@ import React from 'react';
2
2
  import { UInput as UInputElement } from '../components/input/UInput';
3
3
 
4
4
  export declare const UInput: React.ForwardRefExoticComponent<
5
- Partial<UInputElement> & React.HTMLAttributes<UInputElement> & {
5
+ Omit<Partial<UInputElement>, keyof React.HTMLAttributes<UInputElement>>
6
+ & Omit<React.HTMLAttributes<UInputElement>, 'onInput' | 'onChange'>
7
+ & {
6
8
  onInput?: (event: CustomEvent) => void;
7
9
  onChange?: (event: CustomEvent) => void;
8
10
  }
@@ -2,7 +2,9 @@ import React from 'react';
2
2
  import { UMenu as UMenuElement } from '../components/menu/UMenu';
3
3
 
4
4
  export declare const UMenu: React.ForwardRefExoticComponent<
5
- Partial<UMenuElement> & React.HTMLAttributes<UMenuElement> & {
5
+ Omit<Partial<UMenuElement>, keyof React.HTMLAttributes<UMenuElement>>
6
+ & Omit<React.HTMLAttributes<UMenuElement>, 'onChange'>
7
+ & {
6
8
  onChange?: (event: CustomEvent) => void;
7
9
  }
8
10
  >;
@@ -3,7 +3,9 @@ import { UMenuItem as UMenuItemElement } from '../components/menu-item/UMenuItem
3
3
  import { type PickEventDetail } from '../events/PickEvent';
4
4
 
5
5
  export declare const UMenuItem: React.ForwardRefExoticComponent<
6
- Partial<UMenuItemElement> & React.HTMLAttributes<UMenuItemElement> & {
6
+ Omit<Partial<UMenuItemElement>, keyof React.HTMLAttributes<UMenuItemElement>>
7
+ & Omit<React.HTMLAttributes<UMenuItemElement>, 'onPick'>
8
+ & {
7
9
  onPick?: (event: CustomEvent<PickEventDetail>) => void;
8
10
  }
9
11
  >;
@@ -2,7 +2,9 @@ import React from 'react';
2
2
  import { UOption as UOptionElement } from '../components/option/UOption';
3
3
 
4
4
  export declare const UOption: React.ForwardRefExoticComponent<
5
- Partial<UOptionElement> & React.HTMLAttributes<UOptionElement> & {
5
+ Omit<Partial<UOptionElement>, keyof React.HTMLAttributes<UOptionElement>>
6
+ & Omit<React.HTMLAttributes<UOptionElement>, never>
7
+ & {
6
8
  }
7
9
  >;
8
10
 
@@ -2,7 +2,9 @@ import React from 'react';
2
2
  import { UPanel as UPanelElement } from '../components/panel/UPanel';
3
3
 
4
4
  export declare const UPanel: React.ForwardRefExoticComponent<
5
- Partial<UPanelElement> & React.HTMLAttributes<UPanelElement> & {
5
+ Omit<Partial<UPanelElement>, keyof React.HTMLAttributes<UPanelElement>>
6
+ & Omit<React.HTMLAttributes<UPanelElement>, never>
7
+ & {
6
8
  }
7
9
  >;
8
10
 
@@ -2,7 +2,9 @@ import React from 'react';
2
2
  import { UPopover as UPopoverElement } from '../components/popover/UPopover';
3
3
 
4
4
  export declare const UPopover: React.ForwardRefExoticComponent<
5
- Partial<UPopoverElement> & React.HTMLAttributes<UPopoverElement> & {
5
+ Omit<Partial<UPopoverElement>, keyof React.HTMLAttributes<UPopoverElement>>
6
+ & Omit<React.HTMLAttributes<UPopoverElement>, 'onShow' | 'onHide'>
7
+ & {
6
8
  onShow?: (event: CustomEvent) => void;
7
9
  onHide?: (event: CustomEvent) => void;
8
10
  }
@@ -2,7 +2,9 @@ import React from 'react';
2
2
  import { UProgressBar as UProgressBarElement } from '../components/progress-bar/UProgressBar';
3
3
 
4
4
  export declare const UProgressBar: React.ForwardRefExoticComponent<
5
- Partial<UProgressBarElement> & React.HTMLAttributes<UProgressBarElement> & {
5
+ Omit<Partial<UProgressBarElement>, keyof React.HTMLAttributes<UProgressBarElement>>
6
+ & Omit<React.HTMLAttributes<UProgressBarElement>, never>
7
+ & {
6
8
  }
7
9
  >;
8
10
 
@@ -2,7 +2,9 @@ import React from 'react';
2
2
  import { UProgressRing as UProgressRingElement } from '../components/progress-ring/UProgressRing';
3
3
 
4
4
  export declare const UProgressRing: React.ForwardRefExoticComponent<
5
- Partial<UProgressRingElement> & React.HTMLAttributes<UProgressRingElement> & {
5
+ Omit<Partial<UProgressRingElement>, keyof React.HTMLAttributes<UProgressRingElement>>
6
+ & Omit<React.HTMLAttributes<UProgressRingElement>, never>
7
+ & {
6
8
  }
7
9
  >;
8
10
 
@@ -2,7 +2,9 @@ import React from 'react';
2
2
  import { URadio as URadioElement } from '../components/radio/URadio';
3
3
 
4
4
  export declare const URadio: React.ForwardRefExoticComponent<
5
- Partial<URadioElement> & React.HTMLAttributes<URadioElement> & {
5
+ Omit<Partial<URadioElement>, keyof React.HTMLAttributes<URadioElement>>
6
+ & Omit<React.HTMLAttributes<URadioElement>, 'onChange'>
7
+ & {
6
8
  onChange?: (event: CustomEvent) => void;
7
9
  }
8
10
  >;
@@ -2,7 +2,9 @@ import React from 'react';
2
2
  import { URating as URatingElement } from '../components/rating/URating';
3
3
 
4
4
  export declare const URating: React.ForwardRefExoticComponent<
5
- Partial<URatingElement> & React.HTMLAttributes<URatingElement> & {
5
+ Omit<Partial<URatingElement>, keyof React.HTMLAttributes<URatingElement>>
6
+ & Omit<React.HTMLAttributes<URatingElement>, 'onChange'>
7
+ & {
6
8
  onChange?: (event: CustomEvent) => void;
7
9
  }
8
10
  >;
@@ -2,7 +2,9 @@ import React from 'react';
2
2
  import { USelect as USelectElement } from '../components/select/USelect';
3
3
 
4
4
  export declare const USelect: React.ForwardRefExoticComponent<
5
- Partial<USelectElement> & React.HTMLAttributes<USelectElement> & {
5
+ Omit<Partial<USelectElement>, keyof React.HTMLAttributes<USelectElement>>
6
+ & Omit<React.HTMLAttributes<USelectElement>, 'onChange'>
7
+ & {
6
8
  onChange?: (event: CustomEvent) => void;
7
9
  }
8
10
  >;
@@ -2,7 +2,9 @@ import React from 'react';
2
2
  import { USkeleton as USkeletonElement } from '../components/skeleton/USkeleton';
3
3
 
4
4
  export declare const USkeleton: React.ForwardRefExoticComponent<
5
- Partial<USkeletonElement> & React.HTMLAttributes<USkeletonElement> & {
5
+ Omit<Partial<USkeletonElement>, keyof React.HTMLAttributes<USkeletonElement>>
6
+ & Omit<React.HTMLAttributes<USkeletonElement>, never>
7
+ & {
6
8
  }
7
9
  >;
8
10
 
@@ -2,7 +2,9 @@ import React from 'react';
2
2
  import { USlider as USliderElement } from '../components/slider/USlider';
3
3
 
4
4
  export declare const USlider: React.ForwardRefExoticComponent<
5
- Partial<USliderElement> & React.HTMLAttributes<USliderElement> & {
5
+ Omit<Partial<USliderElement>, keyof React.HTMLAttributes<USliderElement>>
6
+ & Omit<React.HTMLAttributes<USliderElement>, 'onChange'>
7
+ & {
6
8
  onChange?: (event: CustomEvent) => void;
7
9
  }
8
10
  >;
@@ -2,7 +2,9 @@ import React from 'react';
2
2
  import { USpinner as USpinnerElement } from '../components/spinner/USpinner';
3
3
 
4
4
  export declare const USpinner: React.ForwardRefExoticComponent<
5
- Partial<USpinnerElement> & React.HTMLAttributes<USpinnerElement> & {
5
+ Omit<Partial<USpinnerElement>, keyof React.HTMLAttributes<USpinnerElement>>
6
+ & Omit<React.HTMLAttributes<USpinnerElement>, never>
7
+ & {
6
8
  }
7
9
  >;
8
10
 
@@ -3,7 +3,9 @@ import { USplitPanel as USplitPanelElement } from '../components/split-panel/USp
3
3
  import { type ShiftEventDetail } from '../events/ShiftEvent';
4
4
 
5
5
  export declare const USplitPanel: React.ForwardRefExoticComponent<
6
- Partial<USplitPanelElement> & React.HTMLAttributes<USplitPanelElement> & {
6
+ Omit<Partial<USplitPanelElement>, keyof React.HTMLAttributes<USplitPanelElement>>
7
+ & Omit<React.HTMLAttributes<USplitPanelElement>, 'onShiftStart' | 'onShift' | 'onShiftEnd'>
8
+ & {
7
9
  onShiftStart?: (event: CustomEvent<ShiftEventDetail>) => void;
8
10
  onShift?: (event: CustomEvent<ShiftEventDetail>) => void;
9
11
  onShiftEnd?: (event: CustomEvent<ShiftEventDetail>) => void;
@@ -2,7 +2,9 @@ import React from 'react';
2
2
  import { USwitch as USwitchElement } from '../components/switch/USwitch';
3
3
 
4
4
  export declare const USwitch: React.ForwardRefExoticComponent<
5
- Partial<USwitchElement> & React.HTMLAttributes<USwitchElement> & {
5
+ Omit<Partial<USwitchElement>, keyof React.HTMLAttributes<USwitchElement>>
6
+ & Omit<React.HTMLAttributes<USwitchElement>, 'onChange'>
7
+ & {
6
8
  onChange?: (event: CustomEvent) => void;
7
9
  }
8
10
  >;
@@ -3,7 +3,9 @@ import { UTab as UTabElement } from '../components/tab/UTab';
3
3
  import { type RemoveEventDetail } from '../events/RemoveEvent';
4
4
 
5
5
  export declare const UTab: React.ForwardRefExoticComponent<
6
- Partial<UTabElement> & React.HTMLAttributes<UTabElement> & {
6
+ Omit<Partial<UTabElement>, keyof React.HTMLAttributes<UTabElement>>
7
+ & Omit<React.HTMLAttributes<UTabElement>, 'onRemove'>
8
+ & {
7
9
  onRemove?: (event: CustomEvent<RemoveEventDetail>) => void;
8
10
  }
9
11
  >;
@@ -2,7 +2,9 @@ import React from 'react';
2
2
  import { UTabPanel as UTabPanelElement } from '../components/tab-panel/UTabPanel';
3
3
 
4
4
  export declare const UTabPanel: React.ForwardRefExoticComponent<
5
- Partial<UTabPanelElement> & React.HTMLAttributes<UTabPanelElement> & {
5
+ Omit<Partial<UTabPanelElement>, keyof React.HTMLAttributes<UTabPanelElement>>
6
+ & Omit<React.HTMLAttributes<UTabPanelElement>, 'onChange'>
7
+ & {
6
8
  onChange?: (event: CustomEvent) => void;
7
9
  }
8
10
  >;
@@ -2,7 +2,9 @@ import React from 'react';
2
2
  import { UTag as UTagElement } from '../components/tag/UTag';
3
3
 
4
4
  export declare const UTag: React.ForwardRefExoticComponent<
5
- Partial<UTagElement> & React.HTMLAttributes<UTagElement> & {
5
+ Omit<Partial<UTagElement>, keyof React.HTMLAttributes<UTagElement>>
6
+ & Omit<React.HTMLAttributes<UTagElement>, never>
7
+ & {
6
8
  }
7
9
  >;
8
10
 
@@ -2,7 +2,9 @@ import React from 'react';
2
2
  import { UTextarea as UTextareaElement } from '../components/textarea/UTextarea';
3
3
 
4
4
  export declare const UTextarea: React.ForwardRefExoticComponent<
5
- Partial<UTextareaElement> & React.HTMLAttributes<UTextareaElement> & {
5
+ Omit<Partial<UTextareaElement>, keyof React.HTMLAttributes<UTextareaElement>>
6
+ & Omit<React.HTMLAttributes<UTextareaElement>, 'onInput' | 'onChange'>
7
+ & {
6
8
  onInput?: (event: CustomEvent) => void;
7
9
  onChange?: (event: CustomEvent) => void;
8
10
  }
@@ -2,7 +2,9 @@ import React from 'react';
2
2
  import { UTooltip as UTooltipElement } from '../components/tooltip/UTooltip';
3
3
 
4
4
  export declare const UTooltip: React.ForwardRefExoticComponent<
5
- Partial<UTooltipElement> & React.HTMLAttributes<UTooltipElement> & {
5
+ Omit<Partial<UTooltipElement>, keyof React.HTMLAttributes<UTooltipElement>>
6
+ & Omit<React.HTMLAttributes<UTooltipElement>, 'onShow' | 'onHide'>
7
+ & {
6
8
  onShow?: (event: CustomEvent) => void;
7
9
  onHide?: (event: CustomEvent) => void;
8
10
  }
@@ -2,7 +2,9 @@ import React from 'react';
2
2
  import { UTree as UTreeElement } from '../components/tree/UTree';
3
3
 
4
4
  export declare const UTree: React.ForwardRefExoticComponent<
5
- Partial<UTreeElement> & React.HTMLAttributes<UTreeElement> & {
5
+ Omit<Partial<UTreeElement>, keyof React.HTMLAttributes<UTreeElement>>
6
+ & Omit<React.HTMLAttributes<UTreeElement>, 'onChange'>
7
+ & {
6
8
  onChange?: (event: CustomEvent) => void;
7
9
  }
8
10
  >;
@@ -6,7 +6,9 @@ import { type PickEventDetail } from '../events/PickEvent';
6
6
  import { type CheckEventDetail } from '../events/CheckEvent';
7
7
 
8
8
  export declare const UTreeItem: React.ForwardRefExoticComponent<
9
- Partial<UTreeItemElement> & React.HTMLAttributes<UTreeItemElement> & {
9
+ Omit<Partial<UTreeItemElement>, keyof React.HTMLAttributes<UTreeItemElement>>
10
+ & Omit<React.HTMLAttributes<UTreeItemElement>, 'onExpand' | 'onCollapse' | 'onPick' | 'onCheck'>
11
+ & {
10
12
  onExpand?: (event: CustomEvent<ExpandEventDetail>) => void;
11
13
  onCollapse?: (event: CustomEvent<CollapseEventDetail>) => void;
12
14
  onPick?: (event: CustomEvent<PickEventDetail>) => void;
@@ -10,11 +10,18 @@ declare function getDefaultBaseUrl(): string;
10
10
  declare function setDefaultBaseUrl(url: string): void;
11
11
  /**
12
12
  * `name`으로 아이콘 리소스를 가져오는 함수입니다.
13
+ *
14
+ * 반환 계약 — 레지스트리가 결과를 (lib, name) 단위로 캐시하므로 리졸버는 순수 조회만 담당합니다:
15
+ * - `string` 반환: 성공 — 세션 동안 캐시되어 재호출되지 않습니다.
16
+ * - `undefined` 반환: **not-found 확정** — 네거티브 캐시되어 재호출되지 않습니다.
17
+ * (탈출구: `IconCache.clear()`)
18
+ * - `throw`: **일시 오류**(네트워크 장애 등) — 캐시되지 않아 다음 조회 시 재시도됩니다.
13
19
  */
14
20
  type IconResolver = (name: string) => string | undefined | Promise<string | undefined>;
15
21
  /**
16
22
  * 아이콘 리소스 캐시입니다.
17
23
  * lib + name 조합으로 SVG 리소스를 저장/조회합니다.
24
+ * `undefined` 값은 "not-found 확정"의 네거티브 캐시 항목을 의미합니다.
18
25
  */
19
26
  declare class IconCache {
20
27
  private static cache;
@@ -22,7 +29,7 @@ declare class IconCache {
22
29
  private constructor();
23
30
  private static makeKey;
24
31
  /**
25
- * 지정된 lib, name 조합의 리소스가 존재하는지 확인합니다.
32
+ * 지정된 lib, name 조합의 리소스(네거티브 항목 포함)가 존재하는지 확인합니다.
26
33
  */
27
34
  static has(lib: string, name: string): boolean;
28
35
  /**
@@ -31,12 +38,15 @@ declare class IconCache {
31
38
  static get(lib: string, name: string): string | undefined;
32
39
  /**
33
40
  * 지정된 lib, name 조합의 리소스를 저장합니다.
41
+ * `undefined`를 저장하면 not-found 확정(네거티브 캐시)으로 기록됩니다.
34
42
  */
35
- static set(lib: string, name: string, resource: string): void;
43
+ static set(lib: string, name: string, resource: string | undefined): void;
36
44
  /**
37
- * 전체 캐시를 비웁니다.
45
+ * 캐시를 비웁니다.
46
+ *
47
+ * @param lib - 지정하면 해당 라이브러리의 항목만, 생략하면 전체를 비웁니다.
38
48
  */
39
- static clear(): void;
49
+ static clear(lib?: string): void;
40
50
  }
41
51
  /**
42
52
  * 아이콘 레지스트리입니다.
@@ -44,6 +54,8 @@ declare class IconCache {
44
54
  */
45
55
  declare class IconRegistry {
46
56
  private static libs;
57
+ /** 동일 (lib, name)의 동시 리졸브를 하나의 요청으로 합치는 in-flight 맵입니다. */
58
+ private static pending;
47
59
  /** 클래스 생성 방지 */
48
60
  private constructor();
49
61
  /**
@@ -57,11 +69,33 @@ declare class IconRegistry {
57
69
  static register(lib: string, resolver: IconResolver): void;
58
70
  /**
59
71
  * 지정된 아이콘 라이브러리를 등록 해제합니다.
72
+ * 해당 라이브러리의 캐시 항목도 함께 비웁니다 —
73
+ * `unregister` 후 다른 리졸버를 재등록할 때 이전 결과가 남지 않도록 하기 위함입니다.
60
74
  */
61
75
  static unregister(lib: string): void;
62
76
  /**
63
77
  * 지정된 아이콘 라이브러리의 svg 아이콘 소스를 가져옵니다.
78
+ *
79
+ * 리졸브 결과는 (lib, name) 단위로 캐시됩니다 — 스트리밍 재렌더처럼 같은 아이콘이
80
+ * 반복 재마운트되는 시나리오에서도 리졸버는 이름당 한 번만 호출됩니다.
81
+ * 동시 요청은 in-flight Promise를 공유하고, `IconResolver` 계약에 따라
82
+ * `undefined`(not-found 확정)는 네거티브 캐시, `throw`(일시 오류)는 캐시하지 않습니다.
64
83
  */
65
84
  static resolve(lib: string, name: string): Promise<string | undefined>;
85
+ /**
86
+ * URL에서 직접 svg 아이콘 소스를 가져옵니다.
87
+ * `u-icon`의 `src` 경로와 무-lib 기본(baseUrl) 경로가 사용하는 저수준 진입점입니다.
88
+ *
89
+ * 결과는 예약 네임스페이스 `url`로 `IconCache`에 캐시되어 같은 URL은 세션당
90
+ * 한 번만 fetch됩니다. HTTP 실패(404 등)는 not-found로 네거티브 캐시되고,
91
+ * 네트워크 오류는 캐시하지 않아 다음 조회 시 재시도됩니다.
92
+ */
93
+ static resolveUrl(url: string): Promise<string | undefined>;
94
+ /**
95
+ * 캐시 기록 + in-flight dedupe + 실패 시멘틱을 공유하는 리졸브 코어입니다.
96
+ * catch를 task 내부에 두어, in-flight를 공유한 모든 호출자가 rejection 대신
97
+ * undefined를 받도록 한다 (렌더 경로로 rejection이 전파되면 안 됨).
98
+ */
99
+ private static resolveCached;
66
100
  }
67
101
  export { getDefaultBaseUrl, setDefaultBaseUrl, IconCache, IconRegistry, };
@@ -63,6 +63,7 @@ var InternalIconBundle = new Map(Object.entries(/* #__PURE__ */ Object.assign({
63
63
  /**
64
64
  * 아이콘 리소스 캐시입니다.
65
65
  * lib + name 조합으로 SVG 리소스를 저장/조회합니다.
66
+ * `undefined` 값은 "not-found 확정"의 네거티브 캐시 항목을 의미합니다.
66
67
  */
67
68
  var IconCache = class {
68
69
  static {
@@ -74,7 +75,7 @@ var IconCache = class {
74
75
  return `${lib}:${name}`;
75
76
  }
76
77
  /**
77
- * 지정된 lib, name 조합의 리소스가 존재하는지 확인합니다.
78
+ * 지정된 lib, name 조합의 리소스(네거티브 항목 포함)가 존재하는지 확인합니다.
78
79
  */
79
80
  static has(lib, name) {
80
81
  return this.cache.has(this.makeKey(lib, name));
@@ -87,15 +88,23 @@ var IconCache = class {
87
88
  }
88
89
  /**
89
90
  * 지정된 lib, name 조합의 리소스를 저장합니다.
91
+ * `undefined`를 저장하면 not-found 확정(네거티브 캐시)으로 기록됩니다.
90
92
  */
91
93
  static set(lib, name, resource) {
92
94
  this.cache.set(this.makeKey(lib, name), resource);
93
95
  }
94
96
  /**
95
- * 전체 캐시를 비웁니다.
97
+ * 캐시를 비웁니다.
98
+ *
99
+ * @param lib - 지정하면 해당 라이브러리의 항목만, 생략하면 전체를 비웁니다.
96
100
  */
97
- static clear() {
98
- this.cache.clear();
101
+ static clear(lib) {
102
+ if (lib === void 0) {
103
+ this.cache.clear();
104
+ return;
105
+ }
106
+ const prefix = `${lib}:`;
107
+ for (const key of this.cache.keys()) if (key.startsWith(prefix)) this.cache.delete(key);
99
108
  }
100
109
  };
101
110
  /**
@@ -106,6 +115,9 @@ var IconRegistry = class {
106
115
  static {
107
116
  this.libs = /* @__PURE__ */ new Map();
108
117
  }
118
+ static {
119
+ this.pending = /* @__PURE__ */ new Map();
120
+ }
109
121
  /** 클래스 생성 방지 */
110
122
  constructor() {}
111
123
  /**
@@ -123,75 +135,95 @@ var IconRegistry = class {
123
135
  }
124
136
  /**
125
137
  * 지정된 아이콘 라이브러리를 등록 해제합니다.
138
+ * 해당 라이브러리의 캐시 항목도 함께 비웁니다 —
139
+ * `unregister` 후 다른 리졸버를 재등록할 때 이전 결과가 남지 않도록 하기 위함입니다.
126
140
  */
127
141
  static unregister(lib) {
128
142
  this.libs.delete(lib);
143
+ IconCache.clear(lib);
129
144
  }
130
145
  /**
131
146
  * 지정된 아이콘 라이브러리의 svg 아이콘 소스를 가져옵니다.
147
+ *
148
+ * 리졸브 결과는 (lib, name) 단위로 캐시됩니다 — 스트리밍 재렌더처럼 같은 아이콘이
149
+ * 반복 재마운트되는 시나리오에서도 리졸버는 이름당 한 번만 호출됩니다.
150
+ * 동시 요청은 in-flight Promise를 공유하고, `IconResolver` 계약에 따라
151
+ * `undefined`(not-found 확정)는 네거티브 캐시, `throw`(일시 오류)는 캐시하지 않습니다.
132
152
  */
133
153
  static async resolve(lib, name) {
154
+ if (IconCache.has(lib, name)) return IconCache.get(lib, name);
134
155
  const resolver = this.libs.get(lib);
135
156
  if (!resolver) return void 0;
136
- return (await resolver(name))?.trim();
157
+ return this.resolveCached(lib, name, () => resolver(name));
158
+ }
159
+ /**
160
+ * URL에서 직접 svg 아이콘 소스를 가져옵니다.
161
+ * `u-icon`의 `src` 경로와 무-lib 기본(baseUrl) 경로가 사용하는 저수준 진입점입니다.
162
+ *
163
+ * 결과는 예약 네임스페이스 `url`로 `IconCache`에 캐시되어 같은 URL은 세션당
164
+ * 한 번만 fetch됩니다. HTTP 실패(404 등)는 not-found로 네거티브 캐시되고,
165
+ * 네트워크 오류는 캐시하지 않아 다음 조회 시 재시도됩니다.
166
+ */
167
+ static async resolveUrl(url) {
168
+ if (IconCache.has("url", url)) return IconCache.get("url", url);
169
+ return this.resolveCached("url", url, async () => {
170
+ const response = await fetch(url);
171
+ if (!response.ok) return void 0;
172
+ return await response.text();
173
+ });
174
+ }
175
+ /**
176
+ * 캐시 기록 + in-flight dedupe + 실패 시멘틱을 공유하는 리졸브 코어입니다.
177
+ * catch를 task 내부에 두어, in-flight를 공유한 모든 호출자가 rejection 대신
178
+ * undefined를 받도록 한다 (렌더 경로로 rejection이 전파되면 안 됨).
179
+ */
180
+ static resolveCached(lib, name, fn) {
181
+ const key = `${lib}:${name}`;
182
+ const inflight = this.pending.get(key);
183
+ if (inflight) return inflight;
184
+ const task = (async () => {
185
+ try {
186
+ const svg = (await fn())?.trim();
187
+ IconCache.set(lib, name, svg);
188
+ return svg;
189
+ } catch (error) {
190
+ console.error(`[IconRegistry] '${key}' 아이콘 리졸브 실패 (재시도 가능):`, error);
191
+ return;
192
+ }
193
+ })();
194
+ this.pending.set(key, task);
195
+ task.finally(() => this.pending.delete(key));
196
+ return task;
137
197
  }
138
198
  };
139
199
  IconRegistry.register("internal", (name) => {
140
200
  return InternalIconBundle.get(name);
141
201
  });
142
202
  IconRegistry.register("tabler", async (name) => {
143
- if (IconCache.has("tabler", name)) return IconCache.get("tabler", name);
144
- try {
145
- const [iconName, style = "outline"] = name.split(":");
146
- const url = `https://cdn.jsdelivr.net/npm/@tabler/icons@3.40.0/icons/${style}/${iconName}.svg`;
147
- const response = await fetch(url);
148
- if (!response.ok) return void 0;
149
- const svg = await response.text();
150
- IconCache.set("tabler", name, svg);
151
- return svg;
152
- } catch {
153
- return;
154
- }
203
+ const [iconName, style = "outline"] = name.split(":");
204
+ const url = `https://cdn.jsdelivr.net/npm/@tabler/icons@3.40.0/icons/${style}/${iconName}.svg`;
205
+ const response = await fetch(url);
206
+ if (!response.ok) return void 0;
207
+ return await response.text();
155
208
  });
156
209
  IconRegistry.register("heroicons", async (name) => {
157
- if (IconCache.has("heroicons", name)) return IconCache.get("heroicons", name);
158
- try {
159
- const [iconName, style = "outline", size = "24"] = name.split(":");
160
- const url = `https://cdn.jsdelivr.net/npm/heroicons@2.2.0/${size}/${style}/${iconName}.svg`;
161
- const response = await fetch(url);
162
- if (!response.ok) return void 0;
163
- const svg = await response.text();
164
- IconCache.set("heroicons", name, svg);
165
- return svg;
166
- } catch {
167
- return;
168
- }
210
+ const [iconName, style = "outline", size = "24"] = name.split(":");
211
+ const url = `https://cdn.jsdelivr.net/npm/heroicons@2.2.0/${size}/${style}/${iconName}.svg`;
212
+ const response = await fetch(url);
213
+ if (!response.ok) return void 0;
214
+ return await response.text();
169
215
  });
170
216
  IconRegistry.register("lucide", async (name) => {
171
- if (IconCache.has("lucide", name)) return IconCache.get("lucide", name);
172
- try {
173
- const url = `https://cdn.jsdelivr.net/npm/lucide-static@0.577.0/icons/${name}.svg`;
174
- const response = await fetch(url);
175
- if (!response.ok) return void 0;
176
- const svg = await response.text();
177
- IconCache.set("lucide", name, svg);
178
- return svg;
179
- } catch {
180
- return;
181
- }
217
+ const url = `https://cdn.jsdelivr.net/npm/lucide-static@0.577.0/icons/${name}.svg`;
218
+ const response = await fetch(url);
219
+ if (!response.ok) return void 0;
220
+ return await response.text();
182
221
  });
183
222
  IconRegistry.register("bootstrap", async (name) => {
184
- if (IconCache.has("bootstrap", name)) return IconCache.get("bootstrap", name);
185
- try {
186
- const url = `https://cdn.jsdelivr.net/npm/bootstrap-icons@1.13.1/icons/${name}.svg`;
187
- const response = await fetch(url);
188
- if (!response.ok) return void 0;
189
- const svg = await response.text();
190
- IconCache.set("bootstrap", name, svg);
191
- return svg;
192
- } catch {
193
- return;
194
- }
223
+ const url = `https://cdn.jsdelivr.net/npm/bootstrap-icons@1.13.1/icons/${name}.svg`;
224
+ const response = await fetch(url);
225
+ if (!response.ok) return void 0;
226
+ return await response.text();
195
227
  });
196
228
  //#endregion
197
229
  export { IconCache, IconRegistry, getDefaultBaseUrl, setDefaultBaseUrl };
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.7.2",
4
+ "version": "1.8.1",
5
5
  "keywords": [
6
6
  "iyulab",
7
7
  "components",
@@ -190,10 +190,21 @@ export const ${className} = createComponent({
190
190
  return ` ${e.reactName}?: (event: ${type}) => void;`;
191
191
  }).join('\n') + '\n'
192
192
  : '';
193
+ // 래퍼 이벤트 prop(onChange 등)은 React.HTMLAttributes 의 동명 핸들러와 교집합되면
194
+ // 어떤 시그니처도 대입 불가가 된다. HTMLAttributes 쪽에서 해당 키를 제거해 CustomEvent
195
+ // 시그니처만 남긴다. 이벤트가 없으면 Omit<…, never> 로 HTMLAttributes 원형 유지.
196
+ const eventKeyUnion = events.length > 0
197
+ ? events.map(e => `'${e.reactName}'`).join(' | ')
198
+ : 'never';
199
+ // Partial<Element> 은 DOM 프로퍼티(children: HTMLCollection 등)를 포함해 React 의 JSX
200
+ // children/이벤트/className 등과 충돌한다. keyof React.HTMLAttributes 를 제거해 컴포넌트
201
+ // 고유 prop만 남기고, React 친화 타입(children: ReactNode 포함)은 HTMLAttributes 가 제공한다.
193
202
  const dts = `${dtsImports.join('\n')}
194
203
 
195
204
  export declare const ${className}: React.ForwardRefExoticComponent<
196
- Partial<${className}Element> & React.HTMLAttributes<${className}Element> & {
205
+ Omit<Partial<${className}Element>, keyof React.HTMLAttributes<${className}Element>>
206
+ & Omit<React.HTMLAttributes<${className}Element>, ${eventKeyUnion}>
207
+ & {
197
208
  ${eventTypes} }
198
209
  >;
199
210
 
@@ -24,14 +24,20 @@ IconRegistry.register('my-icons', async (name: string) => {
24
24
  });
25
25
  ```
26
26
 
27
+ Resolver contract — the registry owns caching, so a resolver is a pure lookup:
28
+ return `string` = success (cached) · return `undefined` = definitive not-found (negative-cached, not retried) · `throw` = transient error (not cached, retried on next lookup).
29
+
30
+ `register()` ignores an already-registered `lib` name. To override a built-in library (e.g. air-gapped local bundle), call `unregister(lib)` first — this also evicts that library's cache.
31
+
27
32
  ## API
28
33
 
29
34
  | Method | Description |
30
35
  |--------|-------------|
31
- | `IconRegistry.register(lib, resolver)` | Register an icon library |
32
- | `IconRegistry.unregister(lib)` | Remove a library |
36
+ | `IconRegistry.register(lib, resolver)` | Register an icon library (ignored if `lib` already registered) |
37
+ | `IconRegistry.unregister(lib)` | Remove a library + evict its cached entries |
33
38
  | `IconRegistry.has(lib)` | Check if a library is registered |
34
- | `IconRegistry.resolve(lib, name)` | `Promise<string \| undefined>` — resolve SVG source |
39
+ | `IconRegistry.resolve(lib, name)` | `Promise<string \| undefined>` — resolve SVG source (cached + in-flight dedupe) |
40
+ | `IconRegistry.resolveUrl(url)` | `Promise<string \| undefined>` — fetch SVG by URL, cached under the reserved `url` namespace (used by `u-icon` `src`/base-URL paths) |
35
41
 
36
42
  ## Built-in Libraries
37
43
 
@@ -40,14 +46,15 @@ IconRegistry.register('my-icons', async (name: string) => {
40
46
  | `internal` | (bundled) | — |
41
47
  | `tabler` | `name` / `name:filled` | 3.40.0 |
42
48
  | `heroicons` | `name` / `name:solid` | 2.2.0 |
43
- | `lucide` | `name` | 0.503.0 |
44
- | `bootstrap` | `name` / `name:filled` | 1.11.3 |
49
+ | `lucide` | `name` | 0.577.0 |
50
+ | `bootstrap` | `name` / `name:filled` | 1.13.1 |
45
51
 
46
52
  ## Caching
47
53
 
48
- Resolved SVGs are cached by `lib:name` key. The cache persists for the lifetime of the page.
54
+ Resolved SVGs are cached by `lib:name` key (owned by `IconRegistry.resolve()` — resolvers are called once per name per session, concurrent lookups share one in-flight request). Not-found results are negative-cached; transient errors (`throw`) are retried. The cache persists for the lifetime of the page.
49
55
 
50
56
  ```ts
51
57
  // Internal cache is managed automatically; manual access is rarely needed.
52
- // IconCache.clear(); // clears all cached entries
58
+ // IconCache.clear(); // clears all cached entries
59
+ // IconCache.clear('tabler'); // clears one library's entries
53
60
  ```