@iyulab/modern-app 0.47.0 → 0.48.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,38 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.48.0] - 2026-10-09
4
+
5
+ ### Changed
6
+
7
+ - Comments inside the components' `css` styles are no longer shipped. A tagged template's body is a string, so a
8
+ consumer's bundler could not remove them; they were design notes, sent to every browser.
9
+
10
+ ### Removed
11
+
12
+ - **Breaking** — `registerLocale()` and `setDefaultLocale()`, deprecated since 0.26.0. The language is chosen in one
13
+ place, `Locale.set()` of `@iyulab/components`; a `setDefaultLocale()` call used to override it for this package only.
14
+ Migrate:
15
+ - `registerLocale(lang, strings)` → `modernAppLocale.register(lang, strings)`. A function-valued
16
+ `wizardStepAnnouncement` becomes a template: `'Step {index} of {total}: {label}'`.
17
+ - `setDefaultLocale(lang)` → `Locale.set(lang)`; `setDefaultLocale(undefined)` has no replacement (there is
18
+ nothing to hand back).
19
+
20
+ ### Documentation
21
+
22
+ - Examples import `Locale`/`Theme`/`formatCurrency` from their own module in `@iyulab/components`
23
+ (`dist/utilities/…`) — the components barrel registers every component even when one value is taken from it.
24
+
25
+ ## [0.47.1] - 2026-10-08
26
+
27
+ ### Fixed
28
+
29
+ - **The default fallback titles the tab like the screen** — "You don’t have access", "Page not found" or
30
+ "Couldn’t open this page" (localized). The tab showed the router's diagnostic message instead
31
+ (`Page not found: http://…/x`, `Access denied: /admin`). Requires `@iyulab/router` 0.21, whose `fallback.title`
32
+ may be a function of the failure; the dependency range is now `^0.21.0`.
33
+ - A route whose `render()` throws `NotFoundError` (the record does not exist) now gets the `not-found` screen —
34
+ router 0.21 passes a `RouteError` from `render()` through instead of wrapping it as a load failure.
35
+
3
36
  ## [0.47.0] - 2026-10-08
4
37
 
5
38
  ### Changed
package/README.md CHANGED
@@ -155,7 +155,7 @@ handed to `Locale` and to `<html lang>`.
155
155
  Register other languages, or your own wording, in the same namespace:
156
156
 
157
157
  ```typescript
158
- import { Locale } from '@iyulab/components';
158
+ import { Locale } from '@iyulab/components/dist/utilities/Locale.js'; // the barrel would register every component
159
159
  import { modernAppLocale } from '@iyulab/modern-app';
160
160
 
161
161
  Locale.set('ko'); // built-in Korean — nothing to register
@@ -169,9 +169,8 @@ modernAppLocale.register('ja', {
169
169
 
170
170
  A partial table is enough — untranslated keys fall back to English, and a registration for a
171
171
  built-in language overrides only the keys it gives. An element's own `locale`
172
- attribute overrides the active language for that element. `registerLocale`/`setDefaultLocale` still
173
- work but are deprecated: a `setDefaultLocale()` call takes precedence over `Locale` until it is
174
- called with `undefined`.
172
+ attribute overrides the active language for that element. The language is chosen in one place —
173
+ `Locale.set()` (the old `registerLocale`/`setDefaultLocale` were removed in 0.48.0).
175
174
 
176
175
  ### 부팅 인증 게이트 (`auth`)
177
176
 
@@ -6,26 +6,26 @@ var t = e`
6
6
  align-items: center;
7
7
  gap: var(--u-space-sm, 8px);
8
8
  padding: var(--u-space-md, 12px) 0;
9
- /* 자기 폭으로 접힌다 — 아래 질의의 기준. 화면 폭이 아니라 자기 상황을 본다.
10
- ⚠**컨테이너는 «자손»을 위한 것이다** — @container 안에서 :host 자신을 겨냥하면
11
- 그 질의는 이 호스트가 아니라 **바깥의 다른 컨테이너**를 본다(실측으로 밟았다:
12
- 360px 에서 접히지 않았다). 그래서 줄바꿈 자체는 여기서 항상 허용하고, 좁을 때의
13
- «폭 채우기»만 자손 규칙으로 건다. */
9
+
10
+
11
+
12
+
13
+
14
14
  flex-wrap: wrap;
15
15
  container-type: inline-size;
16
16
  }
17
17
 
18
- /* 위험 액션(삭제 등)은 **왼쪽 끝**, 주 액션은 오른쪽 끝.
19
- ★같은 무리에 두면 «저장»을 누르려다 «삭제»를 누른다 — 거리가 안전장치다.
20
- 초안(R4)이 *"위험 액션은 시각적으로 분리"* 로 요구한 것이 이 배치다. */
18
+
19
+
20
+
21
21
  .danger {
22
22
  display: flex;
23
23
  align-items: center;
24
24
  gap: var(--u-space-sm, 8px);
25
25
  margin-right: auto;
26
26
  }
27
- /* 빈 슬롯 래퍼는 접는다 — 슬롯 배정은 slotchange 로 추적한다(internals/slotted.ts).
28
- ⚠CSS :has() 로는 못 한다: <slot> 자신이 자식이라 :has(*) 가 항상 참이다. */
27
+
28
+
29
29
  .danger.empty {
30
30
  display: none;
31
31
  }
@@ -37,14 +37,14 @@ var t = e`
37
37
  margin-left: auto;
38
38
  }
39
39
 
40
- /* 화면 하단에 고정 — 긴 폼에서 저장 버튼을 찾아 스크롤하지 않아도 된다.
40
+
41
+
42
+
43
+
44
+
45
+
46
+
41
47
 
42
- 🔴**음수 마진으로 «가장자리까지 늘리는» 것을 하지 않는다.** 첫 판이
43
- margin: 0 calc(-1 * var(--u-space-md, 12px)) 로 좌우를 넘겼는데, 그것은
44
- ***부모가 정확히 그만큼의 가로 패딩을 갖고 있다***고 가정하는 것이다. 실측에서
45
- 그 가정이 깨졌다 — 샘플 화면에서 좌우 12px 씩 넘쳐 **가로 스크롤바가 생기고
46
- 주 액션(저장)이 잘렸다.** 라이브러리가 소비자의 여백을 알 수는 없다.
47
- ⚠이 주석에 백틱을 쓰지 말 것 — css 태그드 템플릿을 그 자리에서 끝낸다. */
48
48
  :host([sticky]) {
49
49
  position: sticky;
50
50
  bottom: 0;
@@ -53,8 +53,8 @@ var t = e`
53
53
  padding: var(--u-space-md, 12px) 0;
54
54
  }
55
55
 
56
- /* 좁을 때: 두 무리가 각자 한 줄을 차지하고 주 액션이 위로 온다.
57
- ⚠주 액션을 아래에 두면 위험 액션이 엄지 위치에 온다 — 순서를 뒤집는 이유다. */
56
+
57
+
58
58
  @container (max-width: 420px) {
59
59
  .main {
60
60
  order: -1;
@@ -11,15 +11,15 @@ var t = e`
11
11
  }
12
12
 
13
13
  .icon {
14
- /* 타입 스케일 밖이다 — 글자가 아니라 장식이라서. 대신 소비자 훅을 연다
15
- (아이콘을 SVG 로 갈아 끼우는 소비자가 크기를 맞출 수 있어야 한다). */
14
+
15
+
16
16
  font-size: var(--empty-state-icon-size, 32px);
17
17
  line-height: 1;
18
18
  margin-bottom: var(--u-space-md, 12px);
19
19
  opacity: 0.7;
20
20
  }
21
21
 
22
- /* 제목과 사유의 묶음 — 오류 변형의 경보 범위. 배치는 호스트와 같은 가운데 세로 줄. */
22
+
23
23
  .message {
24
24
  display: flex;
25
25
  flex-direction: column;
@@ -31,7 +31,7 @@ var t = e`
31
31
  font-size: var(--u-text-subtitle-size, 16px);
32
32
  font-weight: var(--u-text-subtitle-weight, 600);
33
33
  line-height: var(--u-text-subtitle-leading, 1.5);
34
- /* 제목은 본문 색이다 — 빈 상태 전체를 흐리게 만들면 "로딩 중"으로 오독된다. */
34
+
35
35
  color: var(--u-txt-color, #212121);
36
36
  }
37
37
 
@@ -46,10 +46,10 @@ var t = e`
46
46
  gap: var(--u-space-sm, 8px);
47
47
  margin-top: var(--u-space-lg, 16px);
48
48
  }
49
- /* 빈 슬롯 래퍼는 접는다.
50
- ⚠**CSS ':has()' 로는 못 한다** — <slot> 자신이 자식 요소라 ':has(*)' 가 항상 참이다
51
- (실브라우저 테스트로 확인했다. 소스 검사와 jsdom 은 둘 다 통과시킨다).
52
- 배정 상태는 'slotchange' 로 추적해 '.empty' 클래스로 내려온다 — internals/slotted.ts */
49
+
50
+
51
+
52
+
53
53
  .actions.empty {
54
54
  display: none;
55
55
  }
@@ -3,13 +3,13 @@ import { css as e } from "lit";
3
3
  var t = e`
4
4
  :host {
5
5
  display: block;
6
- /* 아래 @container 질의의 기준 — 카드는 «화면»이 아니라 «자기 폭»으로 접힌다.
7
- 사이드바가 열린 1280px 화면의 좁은 본문에서도, 넓은 본문을 가진 태블릿에서도
8
- 같은 판단을 하려면 기준이 자기 폭이어야 한다. */
6
+
7
+
8
+
9
9
  container-type: inline-size;
10
- /* 카드 = 떠 있는 컨테이너(--u-panel-bg-color) — u-card 와 같은 면·반경·선이다.
11
- 페이지는 --u-canvas-bg-color 위에 있으므로 카드는 바탕보다 한 단 밝은 면으로 선다
12
- (종전에는 페이지가 흰색이라 카드를 크롬 틴트 --u-bg-color-raised 로 칠해 구분했다). */
10
+
11
+
12
+
13
13
  background-color: var(--u-panel-bg-color, #FFFFFF);
14
14
  border: 1px solid var(--u-border-color, #E0E0E0);
15
15
  border-radius: var(--u-radius-xl, 8px);
@@ -22,9 +22,9 @@ var t = e`
22
22
  gap: var(--u-space-sm, 8px);
23
23
  padding: var(--u-space-xl, 20px) var(--u-space-xl, 20px) 0;
24
24
  }
25
- /* 제목도 액션도 없으면 헤더를 접는다. ★DOM 에서 빼지 않고 접는 이유 =
26
- 슬롯 배정을 'slotchange' 로 알아야 하는데, 슬롯이 렌더되지 않으면 그 이벤트가
27
- 영영 오지 않는다(자식을 나중에 붙이는 소비자가 헤더를 못 얻는다). */
25
+
26
+
27
+
28
28
  .header.empty {
29
29
  display: none;
30
30
  }
@@ -34,7 +34,7 @@ var t = e`
34
34
  border-bottom: 1px solid var(--u-border-color-weak, #EEEEEE);
35
35
  }
36
36
 
37
- /* 제목과 설명 한 줄을 세로로 묶는다 — 액션은 그 오른쪽에 선다. */
37
+
38
38
  .titles {
39
39
  flex: 1 1 auto;
40
40
  min-width: 0;
@@ -52,8 +52,8 @@ var t = e`
52
52
  letter-spacing: var(--u-text-subtitle-tracking, 0);
53
53
  color: var(--u-txt-color, #212121);
54
54
  }
55
- /* 제목 옆 보조 메타(건수·진척) — 제목보다 한 단 아래. 헤딩 안의 인라인이라 기준선을 공유하고,
56
- 접근성 이름에도 함께 읽힌다. 숫자가 바뀌어도 폭이 흔들리지 않게 고정폭 숫자. */
55
+
56
+
57
57
  .meta {
58
58
  font-size: var(--u-text-body-size, 14px);
59
59
  font-weight: var(--u-text-body-weight, 400);
@@ -69,7 +69,7 @@ var t = e`
69
69
  color: var(--u-txt-color-weak, #616161);
70
70
  }
71
71
 
72
- /* 제목 없이 설명만 있으면 빈 헤딩이 자리를 잡지 않는다. */
72
+
73
73
  .title:empty {
74
74
  display: none;
75
75
  }
@@ -82,20 +82,20 @@ var t = e`
82
82
  font-size: var(--u-text-label-size, 13px);
83
83
  font-weight: var(--u-text-label-weight, 600);
84
84
  }
85
- /* 빈 슬롯 래퍼는 접는다.
86
- ⚠**CSS ':has()' 로는 못 한다** — <slot> 자신이 자식 요소라 ':has(*)' 가 항상 참이다
87
- (실브라우저 테스트로 확인했다. 소스 검사와 jsdom 은 둘 다 통과시킨다).
88
- 배정 상태는 'slotchange' 로 추적해 '.empty' 클래스로 내려온다 — internals/slotted.ts */
85
+
86
+
87
+
88
+
89
89
  .actions.empty {
90
90
  display: none;
91
91
  }
92
92
 
93
- /* 🔴**본문은 세로 흐름이고, 그 리듬을 여기서 정한다.**
94
- 종전에는 padding 만 있어 블록 자식이 **간격 없이 붙었고**, 그보다 나쁘게는
95
- shrink-to-fit 요소들이 각자 다른 폭으로 앉아 **왼쪽 끝이 어긋났다**(실측: 알림 넷을
96
- 나란히 두면 계단처럼 내려갔다). 소비자가 그것을 고치려면 자기 CSS 를 쓰게 되고,
97
- 그 순간 이 패키지가 존재하는 이유가 사라진다.
98
- ⇒ flex column + gap. 자식은 폭을 채우고 간격은 토큰이 정한다. */
93
+
94
+
95
+
96
+
97
+
98
+
99
99
  .body {
100
100
  padding: var(--u-space-xl, 20px);
101
101
  display: flex;
@@ -103,10 +103,10 @@ var t = e`
103
103
  gap: var(--u-space-md, 12px);
104
104
  }
105
105
 
106
- /* 좁을 때: 헤더의 툴바(검색·필터)가 제목 아래로 내려가고 폭을 채운다.
107
- ⚠**제목과 툴바가 한 줄에 남으면 둘 다 읽히지 않는다** — 제목은 잘리고 입력은
108
- 손가락보다 좁아진다. 초안(R4)이 *"좁은 화면에서 접히는 규칙을 라이브러리가 정한다"*
109
- 로 요구한 것이 이 자리다. 480px 는 «제목 + 입력»이 함께 설 수 있는 하한이다. */
106
+
107
+
108
+
109
+
110
110
  @container (max-width: 480px) {
111
111
  .header {
112
112
  flex-wrap: wrap;
@@ -115,13 +115,13 @@ var t = e`
115
115
  width: 100%;
116
116
  }
117
117
  }
118
- /* 표를 카드 가장자리까지 붙일 때. 헤더가 있으면 위 여백만 남긴다 —
119
- 0 으로 만들면 제목과 표가 붙어 읽기 어려워진다. */
118
+
119
+
120
120
  .body.flush {
121
121
  padding: 0;
122
122
  }
123
- /* 헤더가 접혀 있으면 위 여백도 없다 — 인접 선택자는 'display:none' 을 보지 못하므로
124
- 클래스로 갈라야 한다. */
123
+
124
+
125
125
  .header:not(.empty) + .body.flush {
126
126
  padding-top: var(--u-space-md, 12px);
127
127
  }
@@ -19,13 +19,13 @@ var t = e`
19
19
  font-weight: var(--u-text-label-weight, 600);
20
20
  line-height: var(--u-text-body-leading, 1.6);
21
21
  color: var(--u-txt-color, #212121);
22
- /* 긴 값(주소·비고)이 그리드 열을 밀어내지 않게 한다. */
22
+
23
23
  overflow-wrap: anywhere;
24
24
  }
25
25
 
26
- /* 고정폭 숫자만 — 정렬은 건드리지 않는다. 이 요소는 표의 칸이 아니라 라벨-값 한 쌍이라
27
- 세로로 맞출 이웃 숫자가 없다. 우정렬하면 값이 라벨에서 칸 반대편으로 밀려날 뿐이다.
28
- 표처럼 열을 맞춰야 하는 배치라면 소비자가 ::part(value) 로 정렬한다. */
26
+
27
+
28
+
29
29
  .value.numeric {
30
30
  font-variant-numeric: tabular-nums;
31
31
  }
@@ -35,26 +35,26 @@ var t = e`
35
35
  font-weight: var(--u-text-title-weight, 700);
36
36
  }
37
37
 
38
- /* "아직 없음"은 값보다 약하게 — 다만 **읽을 수 있어야 한다.**
39
- ⚠더 흐리게 만들고 싶은 유혹이 있는데, 이 자리가 안 읽히면 사용자는 빈 칸인지
40
- 로딩 중인지 구별하지 못한다. */
38
+
39
+
40
+
41
41
  .value.blank {
42
42
  color: var(--u-txt-color-weak, #616161);
43
43
  font-weight: var(--u-text-body-weight, 400);
44
44
  }
45
45
 
46
- /* size="lg" 보다 우선해야 한다 — blank 는 lg 모드에서도 계속 de-emphasize 상태를 유지해야 한다.
47
- 크기도 본문 단으로 내린다: blank 문구가 «값을 낼 수 없는 사유» 문장일 수 있고, 제목 단에서는
48
- 타일에 담기지 않는다. 대신 값 줄의 높이는 지켜 한 줄에 놓인 타일들의 높이가 갈리지 않게 한다
49
- (lg 값 줄 = 제목 크기 × 본문 행간 — 위 .value 가 행간을 본문 단에 두기 때문이다). */
46
+
47
+
48
+
49
+
50
50
  :host([size="lg"]) .value.blank {
51
51
  font-size: var(--u-text-body-size, 14px);
52
52
  font-weight: var(--u-text-body-weight, 400);
53
53
  min-height: calc(var(--u-text-title-size, 20px) * var(--u-text-body-leading, 1.6));
54
54
  }
55
55
 
56
- /* 단위는 값보다 한 단 아래 — 크기·굵기·색 셋을 함께 낮춰야 KPI 숫자가 강조를 지킨다.
57
- 이 세트를 소비자가 기억해 되돌리게 두면 한 곳이라도 빠진 자리에서 단위가 숫자만큼 커진다. */
56
+
57
+
58
58
  .unit {
59
59
  margin-inline-start: 0.25em;
60
60
  font-size: var(--u-text-label-size, 13px);
@@ -62,9 +62,9 @@ var t = e`
62
62
  color: var(--u-txt-color-weak, #616161);
63
63
  }
64
64
 
65
- /* tone은 trend 유무와 무관하게 값 텍스트 자체에도 적용된다(neutral은 무규칙 — 기본
66
- .value 색과 시각적으로 같은 자리라 별도 규칙을 두면 오히려 "옅어 보이는" 부작용이
67
- 생긴다, .trend.tone-neutral과 달리 .value는 이미 강한 색이 기본값이다). */
65
+
66
+
67
+
68
68
  .value.tone-positive {
69
69
  color: var(--u-success-color-strong, #1B5E20);
70
70
  }
@@ -5,9 +5,9 @@ var t = e`
5
5
  display: block;
6
6
  }
7
7
 
8
- /* ★'auto-fit' + 'minmax' — **컨테이너 폭**이 열 수를 정한다.
9
- 미디어 쿼리로 열 수를 고정하면 사이드 패널(좁은 컨테이너) 안에서 화면 폭 기준으로
10
- 계산돼 칸이 짓눌린다. 이 방식은 어디에 놓여도 자기 폭을 본다. */
8
+
9
+
10
+
11
11
  .grid {
12
12
  display: grid;
13
13
  grid-template-columns: repeat(auto-fit, minmax(var(--info-min, 160px), 1fr));
@@ -6,8 +6,8 @@ var t = e`
6
6
  position: relative;
7
7
  min-width: 0;
8
8
  height: 100%;
9
- /* 부모(페이지 셸)가 높이를 정해 준다는 전제 — GroupBox/ActionBar 와 달리 이 컴포넌트는
10
- "화면을 채우는 셸"이 역할이라 자기 높이를 스스로 갖지 않는다. */
9
+
10
+
11
11
  }
12
12
 
13
13
  .master {
@@ -16,8 +16,8 @@ var t = e`
16
16
  overflow: auto;
17
17
  }
18
18
 
19
- /* 좁을 때(자기 폭 기준, JS 로 잰 'overlay' 속성 — @container 아님, 클래스 상단 주석 참조):
20
- master 가 전체 폭을 채운다. detail 은 그 위 오버레이로 뜬다. */
19
+
20
+
21
21
  :host([overlay]) .master {
22
22
  flex: 1 1 auto;
23
23
  }
@@ -27,9 +27,9 @@ var t = e`
27
27
  width: 0;
28
28
  border-left: 1px solid var(--u-border-color-weak, #EEEEEE);
29
29
  }
30
- /* detail 이 비어 있으면 나눌 것이 없다 — 슬롯 배정은 slotchange 로 추적한다
31
- (internals/slotted.ts). ⚠CSS :has() 로는 못 한다: <slot> 자신이 자식이라 :has(*) 가
32
- 항상 참이다. */
30
+
31
+
32
+
33
33
  .divider.empty,
34
34
  :host([overlay]) .divider {
35
35
  display: none;
@@ -45,8 +45,8 @@ var t = e`
45
45
  display: none;
46
46
  }
47
47
 
48
- /* 오버레이 모드: detail 이 master 위에 뜬다. master 의 flex-basis 를 넘겨받으므로
49
- master 를 가리는 만큼만 자리를 차지하면 된다 — inset:0 로 host 전체를 덮는다. */
48
+
49
+
50
50
  :host([overlay]) .detail {
51
51
  position: absolute;
52
52
  inset: 0;
@@ -55,8 +55,8 @@ var t = e`
55
55
  box-shadow: var(--u-shadow-lg, 0 4px 12px rgba(0, 0, 0, 0.16), 0 2px 4px rgba(0, 0, 0, 0.06));
56
56
  }
57
57
 
58
- /* 작은 닫기 버튼 — 오버레이 모드에서만 보인다. 나란히 배치될 때는 detail 이 이미
59
- 제자리(패널)라 닫을 이유가 없다 — "구조적 기능"의 범위를 오버레이 전환 자체로 좁힌다. */
58
+
59
+
60
60
  .detail-close {
61
61
  display: none;
62
62
  position: absolute;
@@ -68,23 +68,23 @@ var t = e`
68
68
  display: inline-flex;
69
69
  }
70
70
 
71
- /*
72
- * 인쇄 — 오버레이 모드의 detail 은 host 전체를 덮는 절대배치 상자라, 높이가 master 내용에
73
- * 묶이고(그 밖은 잘린다) 닫기 버튼까지 종이에 찍힌다. 화면에 보이는 것(detail)을 흐름에
74
- * 되돌려 전부 인쇄한다 — 덮여 있던 master 는 찍지 않는다. detail 이 비어 있으면 master 가
75
- * 화면 그대로 인쇄된다(covered 는 detail 이 채워졌을 때만 붙는다).
76
- * 나란히 배치 모드는 바꾸지 않는다 — 두 판이 부모 높이를 따라 풀린다.
77
- *
78
- * 🔴**한 판만 보일 때는 flex 뼈대를 걷는다**(오버레이 모드 · detail 없음). flex 호스트와
79
- * overflow: auto 판은 둘 다 독립 서식 문맥이라, 화면 마지막 블록의 아래 여백이 레이아웃
80
- * «안» 에 갇혀 그만큼 높이가 는다 — 내용 끝이 쪽 경계에서 그 여백 이내에 있으면 여백만 담긴
81
- * 빈 꼬리 쪽이 찍힌다(router 아웃렛의 인쇄 수정과 같은 기전). 한 판뿐이면 flex 가 인쇄에서
82
- * 할 일이 없으므로 블록 흐름으로 돌려 여백이 문서 끝까지 접히게 한다(쪽 경계에서 잘린다).
83
- * ⚠**나란히 배치(detail 있음 · 오버레이 아님)는 원리적으로 가둔다** — 두 열은 flex 항목이고
84
- * flex 항목은 항상 독립 서식 문맥이다. 화면에 보인 두 열을 그대로 찍는 것이 계약이다.
85
- * ⚠:state() 를 모르는 엔진에서는 그 선택자만 무효가 되도록 **규칙을 나눴다** — 선택자 목록에
86
- * 섞으면 목록 전체가 버려져 오버레이 규칙까지 사라진다.
87
- */
71
+
72
+
73
+
74
+
75
+
76
+
77
+
78
+
79
+
80
+
81
+
82
+
83
+
84
+
85
+
86
+
87
+
88
88
  @media print {
89
89
  :host([overlay]) {
90
90
  display: block;
@@ -7,13 +7,13 @@ var t = e`
7
7
  gap: var(--u-space-md, 12px);
8
8
  flex-wrap: wrap;
9
9
  margin-bottom: var(--u-space-2xl, 24px);
10
- /* 아래 @container 질의의 기준 — 이 컴포넌트는 «화면»이 아니라 «자기 폭»으로 접힌다. */
10
+
11
11
  container-type: inline-size;
12
12
  }
13
13
 
14
14
  .heading {
15
15
  flex: 1 1 auto;
16
- min-width: 0; /* 긴 제목이 액션을 밀어내지 않게 — 줄바꿈이 아니라 축소가 먼저다 */
16
+ min-width: 0;
17
17
  }
18
18
 
19
19
  .title-row {
@@ -40,7 +40,7 @@ var t = e`
40
40
  color: var(--u-txt-color-weak, #616161);
41
41
  }
42
42
 
43
- /* 제목 위 위치 표시 — 제목보다 네 단 작고 옅다. 링크(브레드크럼)는 상속받은 색으로 선다. */
43
+
44
44
  .eyebrow {
45
45
  margin: 0 0 var(--u-space-2xs, 4px);
46
46
  font-size: var(--u-text-overline-size, 11px);
@@ -63,12 +63,12 @@ var t = e`
63
63
  font-weight: var(--u-text-label-weight, 600);
64
64
  color: var(--u-link-txt-color, #1565C0);
65
65
  text-decoration: none;
66
- /* 타깃 크기(WCAG 2.2 SC 2.5.8) — 줄 상자(1lh)에서 모자란 만큼만 패딩으로 채워 누를 면을 24px 로 만들고,
67
- 같은 크기의 음수 여백으로 그 공간을 돌려준다 — 배치는 움직이지 않는다.
68
- ⚠고정 패딩(4px)이면 안 된다: line-height: normal 의 높이는 글꼴 메트릭이 정해서 글꼴에 따라 17px(→25)도
69
- 15px(→23)도 된다. 줄 상자가 이미 24px 이상이면 패딩은 0 이다. */
66
+
67
+
68
+
69
+
70
70
  display: inline-block;
71
- /* 호스트 하한(--u-target-size)이 있으면 24px 대신 그 값까지 — 폭도 그 값 이상. */
71
+
72
72
  --_back-pad: max(0px, calc((max(24px, var(--u-target-size, 0px)) - 1lh) / 2));
73
73
  padding-block: var(--_back-pad);
74
74
  margin-block: calc(-1 * var(--_back-pad));
@@ -82,12 +82,12 @@ var t = e`
82
82
  text-decoration: underline;
83
83
  }
84
84
 
85
- /* 상태 슬롯이 비어 있으면 자리를 차지하지 않는다 — 빈 배지 자리가 제목을 밀면
86
- 같은 화면인데 제목 위치가 달라 보인다. */
87
- /* 빈 슬롯 래퍼는 접는다.
88
- ⚠**CSS ':has()' 로는 못 한다** — <slot> 자신이 자식 요소라 ':has(*)' 가 항상 참이다
89
- (실브라우저 테스트로 확인했다. 소스 검사와 jsdom 은 둘 다 통과시킨다).
90
- 배정 상태는 'slotchange' 로 추적해 '.empty' 클래스로 내려온다 — internals/slotted.ts */
85
+
86
+
87
+
88
+
89
+
90
+
91
91
  .status.empty {
92
92
  display: none;
93
93
  }
@@ -99,20 +99,20 @@ var t = e`
99
99
  gap: var(--u-space-sm, 8px);
100
100
  margin-left: auto;
101
101
  }
102
- /* 빈 액션 래퍼는 접는다(슬롯 배정은 'slotchange' 로 추적 — internals/slotted.ts). */
102
+
103
103
  .actions.empty {
104
104
  display: none;
105
105
  }
106
106
 
107
- /* 좁을 때: 액션이 제목 아래로 내려가고 **왼쪽 정렬**이 된다.
108
- ★오른쪽 정렬을 유지하면 손가락이 닿기 어려운 쪽으로 몰린다.
107
+
108
+
109
+
110
+
111
+
112
+
113
+
114
+
109
115
 
110
- 🔴**«화면»이 아니라 «자기 폭»을 본다(@container).** 종전에는 @media 였고, 그러면
111
- 이 프리미티브가 **자기가 얼마나 좁은지와 무관하게** 접혔다 — 셸 안에서 사이드바가 열려
112
- 본문이 500px 인 1280px 화면에서는 접히지 않고, 반대로 넓은 본문을 가진 좁은 화면에서는
113
- 불필요하게 접혔다. 컴포넌트가 스스로 판단할 수 있는 것은 **자기 컨테이너 폭**뿐이다.
114
- ⚠container-type: inline-size 는 인라인 축 containment 를 만든다 — 이 호스트는 블록
115
- 레벨이고 자식이 호스트 폭에 기대므로 무해하다(폭별 회귀 테스트가 감시한다). */
116
116
  @container (max-width: 640px) {
117
117
  .actions {
118
118
  margin-left: 0;
@@ -18,8 +18,8 @@ var t = e`
18
18
  :host(:active) {
19
19
  background-color: var(--u-bg-color-active, #EEEEEE);
20
20
  }
21
- /* 켜진 토글 — «현재 페이지»(활성 링크의 진한 면)와 구별되는 옅은 면 + 강조 아이콘. 위치와 설정이 같은 모양이면 섞인다.
22
- 아이콘 색이 상태를 나르므로 라벨이 숨는 슬림 모드에서도 보인다. */
21
+
22
+
23
23
  :host([pressed]) {
24
24
  background-color: var(--app-sidebar-pressed-bg, var(--u-bg-color-active, #EEEEEE));
25
25
  }
@@ -36,7 +36,7 @@ var t = e`
36
36
  justify-content: flex-start;
37
37
  gap: 12px;
38
38
  padding: 8px 12px;
39
- /* 호스트 하한(--u-target-size, 미설정 = 0) — 상자는 border-box(기반 스타일)라 여백을 포함한다. */
39
+
40
40
  min-height: var(--u-target-size, 0px);
41
41
  }
42
42
  button[compact] {
@@ -55,7 +55,7 @@ var t = e`
55
55
  font-size: 20px;
56
56
  }
57
57
 
58
- /* ★SidebarLink 와 같은 자리·같은 단(label). 두 곳이 어긋나면 목록이 계단이 된다. */
58
+
59
59
  span {
60
60
  flex: 1;
61
61
  font-size: var(--u-text-label-size, 13px);
@@ -66,10 +66,10 @@ var t = e`
66
66
  text-overflow: ellipsis;
67
67
  }
68
68
 
69
- /*
70
- * 접힌 상태의 라벨 - 시각적으로만 숨긴다. 접근성 트리에는 남아 항목의 이름이 된다
71
- * (hidden 은 트리에서도 빼서 이름 없는 항목을 만들었다). 절대배치라 flex 배치와 gap 에 끼지 않는다.
72
- */
69
+
70
+
71
+
72
+
73
73
  [part~='label'][compact] {
74
74
  position: absolute;
75
75
  width: 1px;