@iyulab/modern-app 0.18.17 → 0.18.19

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,31 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.18.19] - 2026-09-04
4
+
5
+ ### Added
6
+
7
+ - **`SidebarLayout` had no way for a consumer to control scroll position on route
8
+ change, and no official way to reach its scroll container at all** — only an
9
+ undocumented shadow-DOM `part="main"` selector, a style-only CSS hook rather than a
10
+ JS access contract. Added `layout.scrollBehavior(context, main)`, called on every
11
+ `route-done` before focus moves to the container, and a public `mainElement`
12
+ accessor on the element for reading/writing scroll position outside the hook (e.g.
13
+ saving a position from a `route-begin` listener). Unset `scrollBehavior` does
14
+ nothing, matching Vue Router's own default for an unset `scrollBehavior`.
15
+
16
+ ## [0.18.18] - 2026-09-04
17
+
18
+ ### Fixed
19
+
20
+ - **The React JSX declaration for `<u-info-field>`'s `value` prop was narrowed to
21
+ `string`, while the component's own class field type (`unknown`) and documented
22
+ behavior explicitly accept `null` and numbers.** TypeScript-strict consumers hit
23
+ `TS2322` passing a nullable or numeric value — a very common shape for
24
+ API-sourced data — even though rendering already coerces whatever is passed via
25
+ `String()`/the format helpers. Widened the JSX type to `unknown` to match. Added
26
+ a compile-only regression fixture (`tests/types/react-consumption.tsx`) covering
27
+ the `string | null` and `number | undefined` shapes that previously failed.
28
+
3
29
  ## [0.18.17] - 2026-09-03
4
30
 
5
31
  ### Fixed
@@ -98,7 +98,9 @@ declare module 'react' {
98
98
  interface IntrinsicElements {
99
99
  'u-info-field': React.DetailedHTMLProps<React.HTMLAttributes<HTMLElement>, HTMLElement> & {
100
100
  label?: string;
101
- value?: string;
101
+ /** 클래스 필드와 동일하게 `unknown` — 렌더 로직이 `String(value)`/포맷터로 무엇이
102
+ * 오든 처리하므로 `null`·숫자를 그대로 넘길 수 있다(위 클래스 필드 JSDoc 참조). */
103
+ value?: unknown;
102
104
  blank?: string;
103
105
  numeric?: boolean;
104
106
  format?: InfoFieldFormat;
@@ -14,6 +14,13 @@ export declare class SidebarLayout extends StyledElement<SidebarParts> {
14
14
  progressBarEl: UProgressBar;
15
15
  /** 현재 라우터 컨텍스트 */
16
16
  context: RouteContext | null;
17
+ /**
18
+ * 라우트 컨텐츠의 실제 스크롤 컨테이너(섀도 DOM `[part="main"]`). `scrollTop`을 읽어
19
+ * 위치를 저장하거나, 써서 복원한다 — `part="main"`은 스타일링용 CSS 훅일 뿐 JS 접근
20
+ * 계약이 아니었으므로, 소비자가 이 컨테이너에 안정적으로 접근할 공식 수단으로 신설.
21
+ * 아직 렌더 전이면 `null`.
22
+ */
23
+ get mainElement(): HTMLElement | null;
17
24
  connectedCallback(): void;
18
25
  disconnectedCallback(): void;
19
26
  protected willUpdate(changedProperties: PropertyValues): void;
@@ -40,7 +40,9 @@ var v = class extends i {
40
40
  }, this.handleRouteDone = (e) => {
41
41
  this.progressBarEl.value = 100, setTimeout(() => {
42
42
  this.progressBarEl.removeAttribute("visible");
43
- }, 300), (this.shadowRoot?.querySelector(".main"))?.focus({ preventScroll: !0 });
43
+ }, 300);
44
+ let t = this.shadowRoot?.querySelector(".main");
45
+ t && (this.config?.scrollBehavior?.(e.context, t), t.focus({ preventScroll: !0 }));
44
46
  }, this.handleRouteError = (e) => {
45
47
  this.progressBarEl.status = "error", this.progressBarEl.value = 100, setTimeout(() => {
46
48
  this.progressBarEl.removeAttribute("visible"), this.progressBarEl.status = "default";
@@ -82,6 +84,9 @@ var v = class extends i {
82
84
  static {
83
85
  this.styles = [super.styles, o];
84
86
  }
87
+ get mainElement() {
88
+ return this.shadowRoot?.querySelector(".main") ?? null;
89
+ }
85
90
  connectedCallback() {
86
91
  super.connectedCallback(), window.addEventListener("route-begin", this.handleRouteBegin), window.addEventListener("route-done", this.handleRouteDone), window.addEventListener("route-progress", this.handleRouteProgress), window.addEventListener("route-error", this.handleRouteError), window.addEventListener("screen-resize", this.handleScreenResize);
87
92
  }
@@ -1,4 +1,5 @@
1
1
  import { TemplateResult } from 'lit';
2
+ import { RouteContext } from '@iyulab/router';
2
3
  import { StyleMap } from '../internals/StyledElement';
3
4
  import { SidebarLinkConfig } from '../components/SidebarLink';
4
5
  import { SidebarSectionConfig } from '../components/SidebarSection';
@@ -55,6 +56,14 @@ export interface SidebarLayoutConfig {
55
56
  * 미지정 시 필터링하지 않는다(모든 항목 표시). 보통 `@iyulab/enterprise` 의 `hasPermission` 을 넘긴다.
56
57
  */
57
58
  hasPermission?: (code: string) => boolean;
59
+ /**
60
+ * 라우트 전환이 끝날 때(`route-done`, 포커스가 메인 스크롤 컨테이너로 옮겨지기 직전)
61
+ * 호출된다 — 새 라우트의 `RouteContext`와 그 스크롤 컨테이너(`SidebarLayout.mainElement`와
62
+ * 동일 엘리먼트)를 받는다. 스크롤 위치를 리셋·저장·복원하는 로직은 이 훅 안에서 직접
63
+ * 구현한다(예: 목록→상세로 갔다가 돌아올 때 스크롤 위치 복원). 미지정 시 기본값은
64
+ * "아무것도 안 함" — Vue Router의 `scrollBehavior` 미지정 기본값과 동일하다(breaking 아님).
65
+ */
66
+ scrollBehavior?: (context: RouteContext, main: HTMLElement) => void;
58
67
  /** 사이드바 스타일 맵 */
59
68
  styles?: StyleMap<SidebarParts>;
60
69
  }
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.18.17",
4
+ "version": "0.18.19",
5
5
  "keywords": [
6
6
  "iyulab",
7
7
  "web-framework",
@@ -24,6 +24,9 @@ interface SidebarLayoutConfig {
24
24
  /** Permission filter — hides items (and emptied section/groups) whose requirement fails. Unset shows everything. See "권한 기반 메뉴 필터" below. */
25
25
  hasPermission?: (code: string) => boolean;
26
26
 
27
+ /** Called on `route-done` (before focus moves to the container) with the new route's `RouteContext` and the scroll container itself — implement reset/save/restore here. Unset does nothing (same default as Vue Router's unset `scrollBehavior`). Also reachable outside the hook via the element's `.mainElement` accessor. */
28
+ scrollBehavior?: (context: RouteContext, main: HTMLElement) => void;
29
+
27
30
  /** Per-part style overrides (CSS custom properties / inline styles). */
28
31
  styles?: StyleMap<SidebarParts>;
29
32
  }