@iyulab/modern-app 0.12.0 → 0.14.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,37 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.14.0] - 2026-08-10
4
+
5
+ ### Added
6
+
7
+ - **`SidebarButtonConfig`, `SidebarLinkConfig`, and `SidebarGroupConfig` accept `lib?: string`.**
8
+ When set, `icon` resolves through `@iyulab/components`' `IconRegistry.resolve(lib, name)`
9
+ (a named, registered icon library) instead of only the default URL-fetch fallback path.
10
+ Existing usage without `lib` is unaffected.
11
+
12
+ ### Fixed
13
+
14
+ - **`SidebarLink` selected-state icon color no longer gets overridden by a consumer-set
15
+ icon tint.** Adds a `--link-icon-color` custom property and a `:host([selected]) u-icon`
16
+ rule so the selected state's contrast wins regardless of an inherited or inline icon
17
+ color.
18
+
19
+ ## [0.13.0] - 2026-08-08
20
+
21
+ ### Added
22
+
23
+ - **`u-info-field` accepts `format` (`'number' | 'currency' | 'date'`) and `currency`.**
24
+ When set, the value renders through `@iyulab/components`' `formatNumber`/`formatCurrency`/
25
+ `formatDate` instead of a plain `String(value)`, and `format="number"`/`"currency"` implies
26
+ the existing `numeric` (right-aligned, tabular-nums) styling automatically. `currency` has
27
+ no default — omitting it while `format="currency"` degrades to plain number formatting
28
+ rather than throwing. Existing usage without `format` is unaffected.
29
+
30
+ ### Changed
31
+
32
+ - **`@iyulab/components` dependency floor raised to `^1.27.0`** (the feature above needs
33
+ `formatNumber`/`formatCurrency`/`formatDate`, added in that release).
34
+
3
35
  ## [0.12.0] - 2026-08-07
4
36
 
5
37
  ### Added
@@ -1,5 +1,6 @@
1
1
  import { StyledElement } from '../internals/StyledElement.js';
2
2
  type ElementParts = 'host' | 'label' | 'value';
3
+ export type InfoFieldFormat = 'number' | 'currency' | 'date';
3
4
  /** 값이 "아직 없음"인가 — `0` 과 `false` 는 **값이다**. */
4
5
  export declare function isBlank(v: unknown): boolean;
5
6
  /**
@@ -16,6 +17,7 @@ export declare function isBlank(v: unknown): boolean;
16
17
  * ```html
17
18
  * <u-info-field label="부수" .value=${order.quantity} numeric></u-info-field>
18
19
  * <u-info-field label="거래처">동서인쇄</u-info-field> <!-- 슬롯이 value 를 이긴다 -->
20
+ * <u-info-field label="합계" format="currency" currency="KRW" .value=${order.total}></u-info-field>
19
21
  * ```
20
22
  */
21
23
  export declare class InfoField extends StyledElement<ElementParts> {
@@ -27,7 +29,8 @@ export declare class InfoField extends StyledElement<ElementParts> {
27
29
  * ⚠`0`·`false` 는 **대체되지 않는다** — 값이기 때문이다.
28
30
  * 속성(`value="…"`)과 프로퍼티(`.value=`) 둘 다 받는다 — HTML 속성은 항상 문자열이라
29
31
  * `type: String` 변환기가 붙어도 `.value=${order.quantity}` 같은 비-문자열 프로퍼티
30
- * 바인딩은 그대로 통과한다(변환기는 속성 파싱에만 관여한다). 렌더는 항상 `String(value)`.
32
+ * 바인딩은 그대로 통과한다(변환기는 속성 파싱에만 관여한다). 렌더는 `format` 이 없으면
33
+ * 항상 `String(value)`.
31
34
  */
32
35
  value?: unknown;
33
36
  /** "아직 없음"을 나타낼 문구. */
@@ -35,9 +38,21 @@ export declare class InfoField extends StyledElement<ElementParts> {
35
38
  /**
36
39
  * 숫자 값 — 우정렬 + 고정폭 숫자(`tabular-nums`).
37
40
  * ★자릿수가 세로로 맞아야 크기 비교가 눈으로 된다. LOB 화면은 금액·수량이 절반이다.
41
+ * `format` 이 `'number'`/`'currency'` 면 이 정렬이 자동으로 함의된다 — 따로 켤 필요 없다.
38
42
  */
39
43
  numeric: boolean;
44
+ /**
45
+ * Renders the value through `@iyulab/components`' `formatNumber`/`formatCurrency`/
46
+ * `formatDate`. When unset (default), falls back to plain `String(value)` as before.
47
+ */
48
+ format?: InfoFieldFormat;
49
+ /**
50
+ * Currency code to use when `format="currency"` (e.g. `'KRW'`). **No default** — if
51
+ * omitted, degrades to plain number formatting without a currency symbol (does not throw).
52
+ */
53
+ currency?: string;
40
54
  private get hasSlotted();
55
+ private formatValue;
41
56
  render(): import('lit-html').TemplateResult<1>;
42
57
  }
43
58
  declare global {
@@ -4,11 +4,12 @@ import { StyledElement as n } from "../internals/StyledElement.js";
4
4
  import { styles as r } from "./InfoField.styles.js";
5
5
  import { html as i } from "lit";
6
6
  import { customElement as a, property as o } from "lit/decorators.js";
7
+ import { formatCurrency as s, formatDate as c, formatNumber as l } from "@iyulab/components";
7
8
  //#region src/components/InfoField.ts
8
- function s(e) {
9
+ function u(e) {
9
10
  return e == null || typeof e == "string" && e.trim() === "";
10
11
  }
11
- var c = class extends n {
12
+ var d = class extends n {
12
13
  constructor(...e) {
13
14
  super(...e), this.label = "", this.blank = "—", this.numeric = !1;
14
15
  }
@@ -18,16 +19,19 @@ var c = class extends n {
18
19
  get hasSlotted() {
19
20
  return this.childNodes.length > 0 && [...this.childNodes].some((e) => e.nodeType !== Node.TEXT_NODE || (e.textContent ?? "").trim() !== "");
20
21
  }
22
+ formatValue() {
23
+ return this.format === "currency" ? this.currency ? s(Number(this.value), this.currency) : l(Number(this.value)) : this.format === "number" ? l(Number(this.value)) : this.format === "date" ? c(this.value) : String(this.value);
24
+ }
21
25
  render() {
22
- let e = !this.hasSlotted && s(this.value);
26
+ let e = !this.hasSlotted && u(this.value), t = this.numeric || this.format === "number" || this.format === "currency";
23
27
  return i`
24
28
  <div class="label" part="label">${this.label}</div>
25
- <div class="value ${this.numeric ? "numeric" : ""} ${e ? "blank" : ""}" part="value">
26
- ${this.hasSlotted ? i`<slot></slot>` : e ? this.blank : String(this.value)}
29
+ <div class="value ${t ? "numeric" : ""} ${e ? "blank" : ""}" part="value">
30
+ ${this.hasSlotted ? i`<slot></slot>` : e ? this.blank : this.formatValue()}
27
31
  </div>
28
32
  `;
29
33
  }
30
34
  };
31
- t([o({ type: String }), e("design:type", Object)], c.prototype, "label", void 0), t([o({ type: String }), e("design:type", Object)], c.prototype, "value", void 0), t([o({ type: String }), e("design:type", Object)], c.prototype, "blank", void 0), t([o({ type: Boolean }), e("design:type", Object)], c.prototype, "numeric", void 0), c = t([a("u-info-field")], c);
35
+ t([o({ type: String }), e("design:type", Object)], d.prototype, "label", void 0), t([o({ type: String }), e("design:type", Object)], d.prototype, "value", void 0), t([o({ type: String }), e("design:type", Object)], d.prototype, "blank", void 0), t([o({ type: Boolean }), e("design:type", Object)], d.prototype, "numeric", void 0), t([o({ type: String }), e("design:type", Object)], d.prototype, "format", void 0), t([o({ type: String }), e("design:type", String)], d.prototype, "currency", void 0), d = t([a("u-info-field")], d);
32
36
  //#endregion
33
- export { c as InfoField, s as isBlank };
37
+ export { d as InfoField, u as isBlank };
@@ -7,6 +7,9 @@ type ElementParts = 'host' | 'base' | 'icon' | 'label';
7
7
  export interface SidebarButtonConfig extends SidebarPermissionGuard {
8
8
  type: 'button';
9
9
  icon?: string;
10
+ /** `icon`을 어느 등록 라이브러리에서 찾을지. 미지정 시 `u-icon`의 기본 URL 경로로
11
+ * 해석된다(`IconRegistry.register()`로 등록한 이름 있는 세트를 쓰려면 지정해야 함). */
12
+ lib?: string;
10
13
  label?: string | DirectiveResult;
11
14
  styles?: StyleMap<ElementParts>;
12
15
  onClick?: (event?: Event) => void;
@@ -20,6 +23,8 @@ export declare class SidebarButton extends StyledElement<ElementParts> {
20
23
  compact: boolean;
21
24
  /** 기본 u-icon 경로의 아이콘 이름 */
22
25
  icon?: string;
26
+ /** `icon`을 해석할 등록 라이브러리 이름 */
27
+ lib?: string;
23
28
  /** 버튼 텍스트 라벨 */
24
29
  label?: string | DirectiveResult;
25
30
  render(): import('lit-html').TemplateResult<1>;
@@ -18,6 +18,7 @@ var c = class extends n {
18
18
  return a`
19
19
  <button part="base" ?compact=${this.compact}>
20
20
  <u-icon part="icon"
21
+ .lib=${this.lib}
21
22
  .name=${this.icon}
22
23
  .fallback=${r}
23
24
  ></u-icon>
@@ -31,6 +32,6 @@ var c = class extends n {
31
32
  t([s({
32
33
  type: Boolean,
33
34
  reflect: !0
34
- }), e("design:type", Object)], c.prototype, "compact", void 0), t([s({ type: String }), e("design:type", String)], c.prototype, "icon", void 0), t([s({ type: String }), e("design:type", Object)], c.prototype, "label", void 0), c = t([o("u-sidebar-button")], c);
35
+ }), e("design:type", Object)], c.prototype, "compact", void 0), t([s({ type: String }), e("design:type", String)], c.prototype, "icon", void 0), t([s({ type: String }), e("design:type", String)], c.prototype, "lib", void 0), t([s({ type: String }), e("design:type", Object)], c.prototype, "label", void 0), c = t([o("u-sidebar-button")], c);
35
36
  //#endregion
36
37
  export { c as SidebarButton };
@@ -9,6 +9,7 @@ export interface SidebarGroupConfig extends SidebarPermissionGuard {
9
9
  /** 기본 접힘 상태 */
10
10
  collapsed?: boolean;
11
11
  icon: string;
12
+ lib?: string;
12
13
  label: string | DirectiveResult;
13
14
  items: SidebarLinkConfig[];
14
15
  styles?: StyleMap<ElementParts>;
@@ -26,6 +27,8 @@ export declare class SidebarGroup extends StyledElement<ElementParts> {
26
27
  collapsed: boolean;
27
28
  /** 네비게이션 아이템 데이터 */
28
29
  icon?: string;
30
+ /** `icon`을 해석할 등록 라이브러리 이름 */
31
+ lib?: string;
29
32
  /** 네비게이션 아이템 데이터 */
30
33
  label?: string | DirectiveResult;
31
34
  render(): import('lit-html').TemplateResult<1>;
@@ -30,6 +30,7 @@ var l = class extends n {
30
30
  빠져 있었다.
31
31
  -->
32
32
  <u-icon class="icon" part="icon"
33
+ .lib=${this.lib}
33
34
  .name=${this.icon}
34
35
  .fallback=${r}
35
36
  ></u-icon>
@@ -59,6 +60,6 @@ t([c({
59
60
  }), e("design:type", Boolean)], l.prototype, "selected", void 0), t([c({
60
61
  type: Boolean,
61
62
  reflect: !0
62
- }), e("design:type", Boolean)], l.prototype, "collapsed", void 0), t([c({ type: String }), e("design:type", String)], l.prototype, "icon", void 0), t([c({ type: String }), e("design:type", Object)], l.prototype, "label", void 0), l = t([s("u-sidebar-group")], l);
63
+ }), e("design:type", Boolean)], l.prototype, "collapsed", void 0), t([c({ type: String }), e("design:type", String)], l.prototype, "icon", void 0), t([c({ type: String }), e("design:type", String)], l.prototype, "lib", void 0), t([c({ type: String }), e("design:type", Object)], l.prototype, "label", void 0), l = t([s("u-sidebar-group")], l);
63
64
  //#endregion
64
65
  export { l as SidebarGroup };
@@ -6,6 +6,7 @@ type ElementParts = 'host' | 'base' | 'icon' | 'label';
6
6
  export interface SidebarLinkConfig extends SidebarPermissionGuard {
7
7
  type: 'link';
8
8
  icon?: string;
9
+ lib?: string;
9
10
  label: string | DirectiveResult;
10
11
  href: string;
11
12
  pattern?: string | URLPattern;
@@ -42,6 +43,8 @@ export declare class SidebarLink extends StyledElement<ElementParts> {
42
43
  compact: boolean;
43
44
  /** 네비게이션 아이템 데이터 */
44
45
  icon?: string;
46
+ /** `icon`을 해석할 등록 라이브러리 이름 */
47
+ lib?: string;
45
48
  /** 네비게이션 아이템 데이터 */
46
49
  label?: string | DirectiveResult;
47
50
  /** 네비게이션 링크 */
@@ -23,6 +23,7 @@ var c = class extends n {
23
23
  >
24
24
  <div class="container" part="base" ?compact=${this.compact}>
25
25
  <u-icon part="icon"
26
+ .lib=${this.lib}
26
27
  .name=${this.icon}
27
28
  .fallback=${r}
28
29
  ></u-icon>
@@ -37,6 +38,6 @@ var c = class extends n {
37
38
  t([s({
38
39
  type: Boolean,
39
40
  reflect: !0
40
- }), e("design:type", Object)], c.prototype, "selected", void 0), t([s({ type: Boolean }), e("design:type", Object)], c.prototype, "compact", void 0), t([s({ type: String }), e("design:type", String)], c.prototype, "icon", void 0), t([s({ type: String }), e("design:type", Object)], c.prototype, "label", void 0), t([s({ type: String }), e("design:type", String)], c.prototype, "href", void 0), t([s({ type: String }), e("design:type", Object)], c.prototype, "pattern", void 0), t([s({ type: String }), e("design:type", String)], c.prototype, "navigate", void 0), t([s({ type: String }), e("design:type", String)], c.prototype, "target", void 0), c = t([o("u-sidebar-link")], c);
41
+ }), e("design:type", Object)], c.prototype, "selected", void 0), t([s({ type: Boolean }), e("design:type", Object)], c.prototype, "compact", void 0), t([s({ type: String }), e("design:type", String)], c.prototype, "icon", void 0), t([s({ type: String }), e("design:type", String)], c.prototype, "lib", void 0), t([s({ type: String }), e("design:type", Object)], c.prototype, "label", void 0), t([s({ type: String }), e("design:type", String)], c.prototype, "href", void 0), t([s({ type: String }), e("design:type", Object)], c.prototype, "pattern", void 0), t([s({ type: String }), e("design:type", String)], c.prototype, "navigate", void 0), t([s({ type: String }), e("design:type", String)], c.prototype, "target", void 0), c = t([o("u-sidebar-link")], c);
41
42
  //#endregion
42
43
  export { c as SidebarLink };
@@ -46,9 +46,14 @@ var t = e`
46
46
 
47
47
  u-icon {
48
48
  flex-shrink: 0;
49
- color: inherit;
49
+ color: var(--link-icon-color, inherit);
50
50
  font-size: 20px;
51
51
  }
52
+ /* Selected always wins over any consumer-supplied --link-icon-color — a
53
+ * high-contrast icon on the active background matters more than a brand tint. */
54
+ :host([selected]) u-icon {
55
+ color: var(--app-sidebar-active-fg, var(--u-txt-color-inverse, #FFFFFF));
56
+ }
52
57
 
53
58
  /*
54
59
  * ★내비 항목은 «대상의 짧은 이름» = label 단이다(용도 배정 — DL-157-4).
@@ -157,6 +157,7 @@ var g = class extends i {
157
157
  <u-sidebar-button
158
158
  ?compact=${this.state === "slim"}
159
159
  .icon="${e.icon}"
160
+ .lib="${e.lib}"
160
161
  .label="${e.label}"
161
162
  .styles="${e.styles}"
162
163
  @click="${e.onClick}"
@@ -169,6 +170,7 @@ var g = class extends i {
169
170
  ?compact=${this.state === "slim"}
170
171
  ?selected=${t}
171
172
  .icon="${e.icon}"
173
+ .lib="${e.lib}"
172
174
  .label="${e.label}"
173
175
  .href="${e.href}"
174
176
  .pattern="${e.pattern}"
@@ -195,6 +197,7 @@ var g = class extends i {
195
197
  ?selected=${t}
196
198
  ?collapsed="${e.collapsed ?? !1}"
197
199
  .icon="${e.icon}"
200
+ .lib="${e.lib}"
198
201
  .label="${e.label}"
199
202
  .styles="${e.styles}">
200
203
  ${p(e.items, (e, t) => t, (e) => this.renderItem(e))}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@iyulab/modern-app",
3
3
  "description": "web-framework by iyulab based on lit-element",
4
- "version": "0.12.0",
4
+ "version": "0.14.0",
5
5
  "keywords": [
6
6
  "iyulab",
7
7
  "web-framework",
@@ -53,7 +53,7 @@
53
53
  "typecheck": "tsc --noEmit"
54
54
  },
55
55
  "dependencies": {
56
- "@iyulab/components": "^1.24.0",
56
+ "@iyulab/components": "^1.27.0",
57
57
  "@iyulab/router": "^0.11.0",
58
58
  "i18next": "^26.3.6",
59
59
  "lit": "^3.3.3"
@@ -34,10 +34,16 @@ and those are different states of the business. So the component owns the rule.
34
34
  | `value` | `unknown` | — | | Value; property only (`attribute: false`) |
35
35
  | `blank` | `string` | `'—'` | | Placeholder for null/undefined/empty — **not** for `0` or `false` |
36
36
  | `numeric` | `boolean` | `false` | | Right-align with tabular figures so digits line up vertically |
37
+ | `format` | `'number'\|'currency'\|'date'` | — | | Renders `value` through `@iyulab/components`' `formatNumber`/`formatCurrency`/`formatDate`; unset falls back to plain `String(value)` |
38
+ | `currency` | `string` | — | | Currency code for `format="currency"` (e.g. `'KRW'`); omitted degrades to plain number formatting, does not throw |
37
39
 
38
40
  ⚠ `numeric` earns its keep in **tables**, where columns of figures are compared by eye. On a
39
41
  lone field it just pushes the value away from its label.
40
42
 
43
+ ```html
44
+ <u-info-field label="Total" format="currency" currency="KRW" .value=${order.total}></u-info-field>
45
+ ```
46
+
41
47
  ## CSS Parts
42
48
 
43
49
  | Part | Description |
@@ -39,6 +39,7 @@ interface SidebarLinkConfig {
39
39
  label: string | DirectiveResult;
40
40
  href: string;
41
41
  icon?: string;
42
+ lib?: string;
42
43
  /** Override the URL matching pattern. Accepts a string or URLPattern. */
43
44
  pattern?: string | URLPattern;
44
45
  /** Let the browser navigate instead of the router. For same-origin, non-SPA paths. */
@@ -81,6 +82,7 @@ Collapsible group that contains links.
81
82
  interface SidebarGroupConfig {
82
83
  type: 'group';
83
84
  icon: string;
85
+ lib?: string;
84
86
  label: string | DirectiveResult;
85
87
  items: SidebarLinkConfig[];
86
88
  /** Start collapsed. Default: true */
@@ -143,6 +145,7 @@ Action button — triggers a callback instead of navigating.
143
145
  interface SidebarButtonConfig {
144
146
  type: 'button';
145
147
  icon?: string;
148
+ lib?: string;
146
149
  label: string | DirectiveResult;
147
150
  onClick: () => void;
148
151
  styles?: StyleMap<string>;