@iyulab/modern-app 0.11.1 → 0.13.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,64 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.13.0] - 2026-08-08
4
+
5
+ ### Added
6
+
7
+ - **`u-info-field` accepts `format` (`'number' | 'currency' | 'date'`) and `currency`.**
8
+ When set, the value renders through `@iyulab/components`' `formatNumber`/`formatCurrency`/
9
+ `formatDate` instead of a plain `String(value)`, and `format="number"`/`"currency"` implies
10
+ the existing `numeric` (right-aligned, tabular-nums) styling automatically. `currency` has
11
+ no default — omitting it while `format="currency"` degrades to plain number formatting
12
+ rather than throwing. Existing usage without `format` is unaffected.
13
+
14
+ ### Changed
15
+
16
+ - **`@iyulab/components` dependency floor raised to `^1.27.0`** (the feature above needs
17
+ `formatNumber`/`formatCurrency`/`formatDate`, added in that release).
18
+
19
+ ## [0.12.0] - 2026-08-07
20
+
21
+ ### Added
22
+
23
+ - **`u-info-field` now accepts `value` as a plain HTML attribute**, not just a property binding.
24
+ `value` was declared `attribute: false`, so `<u-info-field value="42">` was silently ignored —
25
+ no error, no warning, the field just stayed blank. Property binding (`.value=${x}`) is
26
+ unaffected and still accepts any type (numbers, dates); the attribute converter only applies
27
+ when the value arrives as a string attribute.
28
+
29
+ - **Sidebar links can point outside the app.** `SidebarLinkConfig` accepted only `href` and could
30
+ not say whether that link was a SPA route. `SidebarLink` rendered a `<u-link>` without passing
31
+ `target` either, so even the one escape the lower layer offered was unreachable from config.
32
+ A same-origin help site placed in the menu had its click intercepted by the router and fell
33
+ through to not-found.
34
+
35
+ Real menus are not all SPA screens: a documentation site, a report endpoint, an admin console
36
+ and a legacy page sit in the same list. `navigate: 'document'` hands the click to the browser;
37
+ `target` is passed through for new-tab links.
38
+
39
+ ```ts
40
+ { type: 'link', icon: 'help', label: 'Help', href: '/help/', navigate: 'document' }
41
+ ```
42
+
43
+ The entry stays an anchor, which is the point — the alternative was a `type: 'button'` with an
44
+ imperative navigation, which loses middle-click and Ctrl+click, address copying, the link role
45
+ for screen readers, and `pattern`-based highlighting, and cannot sit inside a section or group.
46
+
47
+ ### Changed
48
+
49
+ - **`@iyulab/router` requirement raised to `^0.11.0`.** The sidebar `navigate: 'document'` support
50
+ above needs `<u-link navigate="document">`, added in that release; `^0.10.2` does not resolve to
51
+ it (a 0.x range's minor segment behaves like a major boundary under semver).
52
+
53
+ ### Fixed
54
+
55
+ - **`sideEffects` omitted the source-resolved entry barrel.** The allowlist covered the component
56
+ and layout modules but not the barrel that `exports["."]` resolves to. The published artifacts
57
+ were unaffected — the shipped allowlist already covered them — but a consumer resolving this
58
+ package from source (a workspace sibling) could have the barrel elided, dropping the element
59
+ registrations it pulls in. The failure is silent: no error, and unregistered custom elements
60
+ render nothing. The source-form entry points are now declared alongside the artifact ones.
61
+
3
62
  ## [0.11.1] - 2026-08-04
4
63
 
5
64
  ### Fixed
@@ -1,4 +1,4 @@
1
- //#region \0@oxc-project+runtime@0.142.0/helpers/esm/decorate.js
1
+ //#region \0@oxc-project+runtime@0.143.0/helpers/esm/decorate.js
2
2
  function e(e, t, n, r) {
3
3
  var i = arguments.length, a = i < 3 ? t : r === null ? r = Object.getOwnPropertyDescriptor(t, n) : r, o;
4
4
  if (typeof Reflect == "object" && typeof Reflect.decorate == "function") a = Reflect.decorate(e, t, n, r);
@@ -1,4 +1,4 @@
1
- //#region \0@oxc-project+runtime@0.142.0/helpers/esm/decorateMetadata.js
1
+ //#region \0@oxc-project+runtime@0.143.0/helpers/esm/decorateMetadata.js
2
2
  function e(e, t) {
3
3
  if (typeof Reflect == "object" && typeof Reflect.metadata == "function") return Reflect.metadata(e, t);
4
4
  }
@@ -1,5 +1,5 @@
1
- import e from "../_virtual/_@oxc-project_runtime@0.142.0/helpers/esm/decorateMetadata.js";
2
- import t from "../_virtual/_@oxc-project_runtime@0.142.0/helpers/esm/decorate.js";
1
+ import e from "../_virtual/_@oxc-project_runtime@0.143.0/helpers/esm/decorateMetadata.js";
2
+ import t from "../_virtual/_@oxc-project_runtime@0.143.0/helpers/esm/decorate.js";
3
3
  import { StyledElement as n } from "../internals/StyledElement.js";
4
4
  import { slotHasContent as r } from "../internals/slotted.js";
5
5
  import { styles as i } from "./ActionBar.styles.js";
@@ -1,5 +1,5 @@
1
- import e from "../_virtual/_@oxc-project_runtime@0.142.0/helpers/esm/decorateMetadata.js";
2
- import t from "../_virtual/_@oxc-project_runtime@0.142.0/helpers/esm/decorate.js";
1
+ import e from "../_virtual/_@oxc-project_runtime@0.143.0/helpers/esm/decorateMetadata.js";
2
+ import t from "../_virtual/_@oxc-project_runtime@0.143.0/helpers/esm/decorate.js";
3
3
  import { StyledElement as n } from "../internals/StyledElement.js";
4
4
  import { getLocaleStrings as r } from "../internals/locale.js";
5
5
  import { slotHasContent as i } from "../internals/slotted.js";
@@ -1,5 +1,5 @@
1
- import e from "../_virtual/_@oxc-project_runtime@0.142.0/helpers/esm/decorateMetadata.js";
2
- import t from "../_virtual/_@oxc-project_runtime@0.142.0/helpers/esm/decorate.js";
1
+ import e from "../_virtual/_@oxc-project_runtime@0.143.0/helpers/esm/decorateMetadata.js";
2
+ import t from "../_virtual/_@oxc-project_runtime@0.143.0/helpers/esm/decorate.js";
3
3
  import { StyledElement as n } from "../internals/StyledElement.js";
4
4
  import { slotHasContent as r } from "../internals/slotted.js";
5
5
  import { styles as i } from "./GroupBox.styles.js";
@@ -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> {
@@ -25,6 +27,10 @@ export declare class InfoField extends StyledElement<ElementParts> {
25
27
  /**
26
28
  * 값. `null`/`undefined`/빈 문자열이면 `blank` 문구로 대체된다.
27
29
  * ⚠`0`·`false` 는 **대체되지 않는다** — 값이기 때문이다.
30
+ * 속성(`value="…"`)과 프로퍼티(`.value=`) 둘 다 받는다 — HTML 속성은 항상 문자열이라
31
+ * `type: String` 변환기가 붙어도 `.value=${order.quantity}` 같은 비-문자열 프로퍼티
32
+ * 바인딩은 그대로 통과한다(변환기는 속성 파싱에만 관여한다). 렌더는 `format` 이 없으면
33
+ * 항상 `String(value)`.
28
34
  */
29
35
  value?: unknown;
30
36
  /** "아직 없음"을 나타낼 문구. */
@@ -32,9 +38,21 @@ export declare class InfoField extends StyledElement<ElementParts> {
32
38
  /**
33
39
  * 숫자 값 — 우정렬 + 고정폭 숫자(`tabular-nums`).
34
40
  * ★자릿수가 세로로 맞아야 크기 비교가 눈으로 된다. LOB 화면은 금액·수량이 절반이다.
41
+ * `format` 이 `'number'`/`'currency'` 면 이 정렬이 자동으로 함의된다 — 따로 켤 필요 없다.
35
42
  */
36
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;
37
54
  private get hasSlotted();
55
+ private formatValue;
38
56
  render(): import('lit-html').TemplateResult<1>;
39
57
  }
40
58
  declare global {
@@ -1,14 +1,15 @@
1
- import e from "../_virtual/_@oxc-project_runtime@0.142.0/helpers/esm/decorateMetadata.js";
2
- import t from "../_virtual/_@oxc-project_runtime@0.142.0/helpers/esm/decorate.js";
1
+ import e from "../_virtual/_@oxc-project_runtime@0.143.0/helpers/esm/decorateMetadata.js";
2
+ import t from "../_virtual/_@oxc-project_runtime@0.143.0/helpers/esm/decorate.js";
3
3
  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({ attribute: !1 }), 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 };
@@ -10,7 +10,7 @@ type ElementParts = 'host' | 'grid';
10
10
  *
11
11
  * ```html
12
12
  * <u-info-section>
13
- * <u-info-field label="파트" value="일반"></u-info-field>
13
+ * <u-info-field label="파트" .value=${'일반'}></u-info-field>
14
14
  * <u-info-field label="부수" .value=${0} numeric></u-info-field>
15
15
  * </u-info-section>
16
16
  * ```
@@ -1,5 +1,5 @@
1
- import e from "../_virtual/_@oxc-project_runtime@0.142.0/helpers/esm/decorateMetadata.js";
2
- import t from "../_virtual/_@oxc-project_runtime@0.142.0/helpers/esm/decorate.js";
1
+ import e from "../_virtual/_@oxc-project_runtime@0.143.0/helpers/esm/decorateMetadata.js";
2
+ import t from "../_virtual/_@oxc-project_runtime@0.143.0/helpers/esm/decorate.js";
3
3
  import { StyledElement as n } from "../internals/StyledElement.js";
4
4
  import { styles as r } from "./InfoSection.styles.js";
5
5
  import { html as i } from "lit";
@@ -1,5 +1,5 @@
1
- import e from "../_virtual/_@oxc-project_runtime@0.142.0/helpers/esm/decorateMetadata.js";
2
- import t from "../_virtual/_@oxc-project_runtime@0.142.0/helpers/esm/decorate.js";
1
+ import e from "../_virtual/_@oxc-project_runtime@0.143.0/helpers/esm/decorateMetadata.js";
2
+ import t from "../_virtual/_@oxc-project_runtime@0.143.0/helpers/esm/decorate.js";
3
3
  import { StyledElement as n } from "../internals/StyledElement.js";
4
4
  import { getLocaleStrings as r } from "../internals/locale.js";
5
5
  import { slotHasContent as i } from "../internals/slotted.js";
@@ -1,5 +1,5 @@
1
- import e from "../_virtual/_@oxc-project_runtime@0.142.0/helpers/esm/decorateMetadata.js";
2
- import t from "../_virtual/_@oxc-project_runtime@0.142.0/helpers/esm/decorate.js";
1
+ import e from "../_virtual/_@oxc-project_runtime@0.143.0/helpers/esm/decorateMetadata.js";
2
+ import t from "../_virtual/_@oxc-project_runtime@0.143.0/helpers/esm/decorate.js";
3
3
  import { StyledElement as n } from "../internals/StyledElement.js";
4
4
  import { DEFAULT_NAV_ICON as r } from "../internals/nav-icon.js";
5
5
  import { styles as i } from "./SidebarButton.styles.js";
@@ -1,5 +1,5 @@
1
- import e from "../_virtual/_@oxc-project_runtime@0.142.0/helpers/esm/decorateMetadata.js";
2
- import t from "../_virtual/_@oxc-project_runtime@0.142.0/helpers/esm/decorate.js";
1
+ import e from "../_virtual/_@oxc-project_runtime@0.143.0/helpers/esm/decorateMetadata.js";
2
+ import t from "../_virtual/_@oxc-project_runtime@0.143.0/helpers/esm/decorate.js";
3
3
  import { StyledElement as n } from "../internals/StyledElement.js";
4
4
  import { DEFAULT_NAV_ICON as r } from "../internals/nav-icon.js";
5
5
  import { SidebarLink as i } from "./SidebarLink.js";
@@ -10,6 +10,25 @@ export interface SidebarLinkConfig extends SidebarPermissionGuard {
10
10
  href: string;
11
11
  pattern?: string | URLPattern;
12
12
  styles?: StyleMap<ElementParts>;
13
+ /**
14
+ * 라우터가 가로채지 않고 브라우저의 문서 이동에 맡긴다 — 같은 오리진의 비-SPA 경로용.
15
+ *
16
+ * 실무 앱의 메뉴에는 SPA 화면만 서지 않는다: 도움말/문서 사이트, 리포트 출력 엔드포인트,
17
+ * 관리 콘솔, 레거시 페이지가 같은 목록에 선다. 종전에는 그것을 **표현할 수 없어서**
18
+ * 메뉴에 걸면 라우터가 클릭을 가로채 not-found 로 떨어졌다.
19
+ *
20
+ * ```ts
21
+ * { type: 'link', label: '도움말', href: '/help/', navigate: 'document' }
22
+ * ```
23
+ */
24
+ navigate?: 'router' | 'document';
25
+ /**
26
+ * 앵커 `target`. 새 탭으로 열 때 쓴다.
27
+ *
28
+ * ⚠**`navigate` 를 대신하지 못한다** — `_blank` 는 **새 탭을 강제**하므로 「같은 탭에서
29
+ * 다른 문서로」를 표현할 수 없다. 그것이 종전에 유일한 탈출구였고, 그래서 부족했다.
30
+ */
31
+ target?: '_self' | '_blank';
13
32
  }
14
33
  /**
15
34
  * SidebarLink 컴포넌트는 계층형 네비게이션 아이템을 표시합니다.
@@ -29,6 +48,10 @@ export declare class SidebarLink extends StyledElement<ElementParts> {
29
48
  href?: string;
30
49
  /** 네비게이션 패턴 매칭 */
31
50
  pattern?: string | URLPattern;
51
+ /** 라우터가 가로채지 않고 브라우저 문서 이동에 맡긴다 (같은 오리진의 비-SPA 경로) */
52
+ navigate?: 'router' | 'document';
53
+ /** 앵커 target — 새 탭으로 열 때 */
54
+ target?: '_self' | '_blank';
32
55
  render(): import('lit-html').TemplateResult<1>;
33
56
  }
34
57
  export {};
@@ -1,5 +1,5 @@
1
- import e from "../_virtual/_@oxc-project_runtime@0.142.0/helpers/esm/decorateMetadata.js";
2
- import t from "../_virtual/_@oxc-project_runtime@0.142.0/helpers/esm/decorate.js";
1
+ import e from "../_virtual/_@oxc-project_runtime@0.143.0/helpers/esm/decorateMetadata.js";
2
+ import t from "../_virtual/_@oxc-project_runtime@0.143.0/helpers/esm/decorate.js";
3
3
  import { StyledElement as n } from "../internals/StyledElement.js";
4
4
  import { DEFAULT_NAV_ICON as r } from "../internals/nav-icon.js";
5
5
  import { styles as i } from "./SidebarLink.styles.js";
@@ -16,7 +16,11 @@ var c = class extends n {
16
16
  }
17
17
  render() {
18
18
  return a`
19
- <u-link .href=${this.href || "#"}>
19
+ <u-link
20
+ .href=${this.href || "#"}
21
+ .navigate=${this.navigate}
22
+ .target=${this.target}
23
+ >
20
24
  <div class="container" part="base" ?compact=${this.compact}>
21
25
  <u-icon part="icon"
22
26
  .name=${this.icon}
@@ -33,6 +37,6 @@ var c = class extends n {
33
37
  t([s({
34
38
  type: Boolean,
35
39
  reflect: !0
36
- }), 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), c = t([o("u-sidebar-link")], c);
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);
37
41
  //#endregion
38
42
  export { c as SidebarLink };
@@ -1,5 +1,5 @@
1
- import e from "../_virtual/_@oxc-project_runtime@0.142.0/helpers/esm/decorateMetadata.js";
2
- import t from "../_virtual/_@oxc-project_runtime@0.142.0/helpers/esm/decorate.js";
1
+ import e from "../_virtual/_@oxc-project_runtime@0.143.0/helpers/esm/decorateMetadata.js";
2
+ import t from "../_virtual/_@oxc-project_runtime@0.143.0/helpers/esm/decorate.js";
3
3
  import { StyledElement as n } from "../internals/StyledElement.js";
4
4
  import { styles as r } from "./SidebarSection.styles.js";
5
5
  import { html as i } from "lit";
@@ -1,5 +1,5 @@
1
- import e from "../_virtual/_@oxc-project_runtime@0.142.0/helpers/esm/decorateMetadata.js";
2
- import t from "../_virtual/_@oxc-project_runtime@0.142.0/helpers/esm/decorate.js";
1
+ import e from "../_virtual/_@oxc-project_runtime@0.143.0/helpers/esm/decorateMetadata.js";
2
+ import t from "../_virtual/_@oxc-project_runtime@0.143.0/helpers/esm/decorate.js";
3
3
  import { property as n } from "lit/decorators.js";
4
4
  import { UElement as r } from "@iyulab/components/dist/components/UElement.js";
5
5
  //#region src/internals/StyledElement.ts
@@ -12,11 +12,13 @@ var i = class extends r {
12
12
  }
13
13
  applyToParts(e) {
14
14
  let t = Object.entries(e);
15
- for (let [e, n] of t) if (n) if (e === "host") this.assignToObject(this, n);
16
- else {
17
- let t = this.renderRoot?.querySelector(`[part="${String(e)}"]`);
18
- if (!t) continue;
19
- this.assignToObject(t, n);
15
+ for (let [e, n] of t) if (n) {
16
+ if (e === "host") this.assignToObject(this, n);
17
+ else {
18
+ let t = this.renderRoot?.querySelector(`[part="${String(e)}"]`);
19
+ if (!t) continue;
20
+ this.assignToObject(t, n);
21
+ }
20
22
  }
21
23
  }
22
24
  assignToObject(e, t) {
@@ -1,7 +1,7 @@
1
1
  import { app as e } from "../App.js";
2
2
  import { filterSidebarItems as t } from "./filterSidebarItems.js";
3
- import n from "../_virtual/_@oxc-project_runtime@0.142.0/helpers/esm/decorateMetadata.js";
4
- import r from "../_virtual/_@oxc-project_runtime@0.142.0/helpers/esm/decorate.js";
3
+ import n from "../_virtual/_@oxc-project_runtime@0.143.0/helpers/esm/decorateMetadata.js";
4
+ import r from "../_virtual/_@oxc-project_runtime@0.143.0/helpers/esm/decorate.js";
5
5
  import { StyledElement as i } from "../internals/StyledElement.js";
6
6
  import "../components/SidebarSection.js";
7
7
  import "../components/SidebarLink.js";
@@ -172,6 +172,8 @@ var g = class extends i {
172
172
  .label="${e.label}"
173
173
  .href="${e.href}"
174
174
  .pattern="${e.pattern}"
175
+ .navigate="${e.navigate}"
176
+ .target="${e.target}"
175
177
  .styles="${e.styles}"
176
178
  ></u-sidebar-link>
177
179
  `;
@@ -5,14 +5,16 @@ function e(e, t) {
5
5
  function t(n, r) {
6
6
  if (!r) return n;
7
7
  let i = r, a = [];
8
- for (let r of n) if (r && e(r, i)) if (r.type === "section" || r.type === "group") {
9
- let e = t(r.items, i);
10
- if (e.length === 0) continue;
11
- a.push({
12
- ...r,
13
- items: e
14
- });
15
- } else a.push(r);
8
+ for (let r of n) if (r && e(r, i)) {
9
+ if (r.type === "section" || r.type === "group") {
10
+ let e = t(r.items, i);
11
+ if (e.length === 0) continue;
12
+ a.push({
13
+ ...r,
14
+ items: e
15
+ });
16
+ } else a.push(r);
17
+ }
16
18
  return a;
17
19
  }
18
20
  //#endregion
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.11.1",
4
+ "version": "0.13.0",
5
5
  "keywords": [
6
6
  "iyulab",
7
7
  "web-framework",
@@ -23,6 +23,17 @@
23
23
  ],
24
24
  "type": "module",
25
25
  "types": "dist/index.d.ts",
26
+ "engines": {
27
+ "node": ">=20"
28
+ },
29
+ "sideEffects": [
30
+ "./dist/components/*.js",
31
+ "./src/components/*.ts",
32
+ "./dist/index.js",
33
+ "./src/index.ts",
34
+ "./dist/layouts/*.js",
35
+ "./src/layouts/*.ts"
36
+ ],
26
37
  "exports": {
27
38
  ".": {
28
39
  "types": "./dist/index.d.ts",
@@ -42,8 +53,8 @@
42
53
  "typecheck": "tsc --noEmit"
43
54
  },
44
55
  "dependencies": {
45
- "@iyulab/components": "^1.24.0",
46
- "@iyulab/router": "^0.10.2",
56
+ "@iyulab/components": "^1.27.0",
57
+ "@iyulab/router": "^0.11.0",
47
58
  "i18next": "^26.3.6",
48
59
  "lit": "^3.3.3"
49
60
  },
@@ -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 |
@@ -41,6 +41,10 @@ interface SidebarLinkConfig {
41
41
  icon?: string;
42
42
  /** Override the URL matching pattern. Accepts a string or URLPattern. */
43
43
  pattern?: string | URLPattern;
44
+ /** Let the browser navigate instead of the router. For same-origin, non-SPA paths. */
45
+ navigate?: 'router' | 'document';
46
+ /** Anchor target. Use for opening in a new tab. */
47
+ target?: '_self' | '_blank';
44
48
  styles?: StyleMap<'host' | 'base' | 'icon' | 'label'>;
45
49
  }
46
50
  ```
@@ -51,6 +55,22 @@ Example:
51
55
  { type: 'link', icon: 'dashboard', label: 'Dashboard', href: '/' }
52
56
  ```
53
57
 
58
+ #### Linking outside the app
59
+
60
+ Not every entry in a real menu is a SPA screen. A help site, a report endpoint, an
61
+ admin console, a legacy page — these sit in the same list. Mark them so the router
62
+ leaves the click alone:
63
+
64
+ ```typescript
65
+ { type: 'link', icon: 'help', label: 'Help', href: '/help/', navigate: 'document' }
66
+ ```
67
+
68
+ The entry stays an anchor, which is the point: middle-click and Ctrl+click still open
69
+ a tab, the address is copyable, screen readers announce a link, `pattern`-based
70
+ highlighting still works, and it can live inside a section or group. `target: '_blank'`
71
+ is available too, but it is not a substitute — it forces a new tab, so same-tab
72
+ navigation to another document needs `navigate`.
73
+
54
74
  ---
55
75
 
56
76
  ### `SidebarGroupConfig` — `type: 'group'`