@iyulab/modern-app 0.45.0 → 0.46.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 +19 -0
- package/dist/components/EmptyState.d.ts +10 -3
- package/dist/components/EmptyState.js +23 -14
- package/dist/index.d.ts +2 -0
- package/dist/index.js +3 -2
- package/dist/internals/locale.d.ts +3 -0
- package/dist/internals/locale.js +6 -0
- package/dist/react.d.ts +3 -1
- package/package.json +1 -1
- package/skills/modern-app/references/api.md +5 -2
- package/skills/modern-app/references/components/empty-state.md +26 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,24 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.46.0] - 2026-10-08
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- **`u-empty-state variant="no-access"`** — a screen this user may not see (a route `enter` guard returned
|
|
8
|
+
`false`). It is the fourth fact next to `no-data`, `no-results` and `error`, with its own next step (asking for
|
|
9
|
+
access) and its own default wording (English and Korean built in, `noAccessTitle`/`noAccessDescription` in
|
|
10
|
+
`modernAppLocale`). Unlike `error` it is not announced as an alert — a 403 is not an outage.
|
|
11
|
+
- **The router's error classes and route types are exported from this package** — `RouteError`,
|
|
12
|
+
`AccessDeniedError`, `NotFoundError`, `ContentLoadError`, `ContentRenderError`, `OutletMissingError` and the
|
|
13
|
+
`RouteConfig`/`RouteContext`/`FallbackRouteConfig` types. A `fallback.render(ctx)` can tell a blocked screen from a
|
|
14
|
+
missing one (`ctx.error instanceof AccessDeniedError`) without declaring `@iyulab/router` itself.
|
|
15
|
+
- `EmptyStateVariant` type.
|
|
16
|
+
|
|
17
|
+
### Fixed
|
|
18
|
+
|
|
19
|
+
- The React `IntrinsicElements` entry for `<u-empty-state>` (from `@iyulab/modern-app/react`) accepted only
|
|
20
|
+
`no-data` and `no-results`, so `variant="error"` did not type-check in JSX. It now uses the element's own type.
|
|
21
|
+
|
|
3
22
|
## [0.45.0] - 2026-10-08
|
|
4
23
|
|
|
5
24
|
### Changed (breaking)
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { StyledElement } from '../internals/StyledElement.js';
|
|
2
2
|
type ElementParts = 'host' | 'icon' | 'message' | 'title' | 'description' | 'actions';
|
|
3
|
+
/** 빈 상태가 전하는 사실 — 다음 행동이 갈린다(`EmptyState.variant` 참조). */
|
|
4
|
+
export type EmptyStateVariant = 'no-data' | 'no-results' | 'error' | 'no-access';
|
|
3
5
|
/**
|
|
4
6
|
* 빈 상태 — 목록·검색 결과가 비었을 때.
|
|
5
7
|
*
|
|
@@ -19,6 +21,9 @@ type ElementParts = 'host' | 'icon' | 'message' | 'title' | 'description' | 'act
|
|
|
19
21
|
* <u-empty-state variant="error" .description=${error.message}>
|
|
20
22
|
* <u-button slot="actions" @click=${retry}>Try again</u-button>
|
|
21
23
|
* </u-empty-state>
|
|
24
|
+
*
|
|
25
|
+
* <!-- 라우트 가드가 막았다(403) — 장애가 아니라 권한의 사실이다. -->
|
|
26
|
+
* <u-empty-state variant="no-access"></u-empty-state>
|
|
22
27
|
* ```
|
|
23
28
|
*
|
|
24
29
|
* ⚠**기본 문구는 영어다** — 이 패키지는 범용 층이라 특정 언어를 기본값으로 가질 수 없다.
|
|
@@ -32,10 +37,12 @@ type ElementParts = 'host' | 'icon' | 'message' | 'title' | 'description' | 'act
|
|
|
32
37
|
export declare class EmptyState extends StyledElement<ElementParts> {
|
|
33
38
|
static styles: import('lit').CSSResultGroup[];
|
|
34
39
|
/**
|
|
35
|
-
* `no-data` = 아직 없음 / `no-results` = 조건에 맞는 것이 없음 / `error` = 불러오지
|
|
36
|
-
*
|
|
40
|
+
* `no-data` = 아직 없음 / `no-results` = 조건에 맞는 것이 없음 / `error` = 불러오지 못함 /
|
|
41
|
+
* `no-access` = 있지만 이 사용자에게 보여 줄 수 없음(라우트 가드의 거부 · 403).
|
|
42
|
+
* 넷은 다음 행동이 다르다(만들기 · 조건 바꾸기 · 다시 시도 · 권한 요청) — `error` 의 제목은 보조기기에 알린다(`role="alert"`).
|
|
43
|
+
* `no-access` 는 알리지 않는다 — 장애가 아니라 화면의 내용이고, 라우트 완료 때 본문으로 가는 포커스가 그것을 읽게 한다.
|
|
37
44
|
*/
|
|
38
|
-
variant:
|
|
45
|
+
variant: EmptyStateVariant;
|
|
39
46
|
/** 제목. 비우면 variant 기본 문구. */
|
|
40
47
|
title: string;
|
|
41
48
|
/** 보조 설명. 비우면 variant 기본 문구. */
|
|
@@ -16,19 +16,28 @@ var d = class extends n {
|
|
|
16
16
|
}
|
|
17
17
|
get defaults() {
|
|
18
18
|
let e = r(this.locale || void 0);
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
19
|
+
switch (this.variant) {
|
|
20
|
+
case "error": return {
|
|
21
|
+
icon: "⚠️",
|
|
22
|
+
title: e.errorTitle,
|
|
23
|
+
description: e.errorDescription
|
|
24
|
+
};
|
|
25
|
+
case "no-access": return {
|
|
26
|
+
icon: "🔒",
|
|
27
|
+
title: e.noAccessTitle,
|
|
28
|
+
description: e.noAccessDescription
|
|
29
|
+
};
|
|
30
|
+
case "no-results": return {
|
|
31
|
+
icon: "🔍",
|
|
32
|
+
title: e.noResultsTitle,
|
|
33
|
+
description: e.noResultsDescription
|
|
34
|
+
};
|
|
35
|
+
default: return {
|
|
36
|
+
icon: "📄",
|
|
37
|
+
title: e.noDataTitle,
|
|
38
|
+
description: e.noDataDescription
|
|
39
|
+
};
|
|
40
|
+
}
|
|
32
41
|
}
|
|
33
42
|
render() {
|
|
34
43
|
let e = this.defaults, t = this.description || e.description;
|
|
@@ -53,6 +62,6 @@ var d = class extends n {
|
|
|
53
62
|
t([l({
|
|
54
63
|
type: String,
|
|
55
64
|
reflect: !0
|
|
56
|
-
}), e("design:type",
|
|
65
|
+
}), e("design:type", Object)], d.prototype, "variant", void 0), t([l({ type: String }), e("design:type", Object)], d.prototype, "title", void 0), t([l({ type: String }), e("design:type", Object)], d.prototype, "description", void 0), t([l({ type: String }), e("design:type", Object)], d.prototype, "locale", void 0), t([u(), e("design:type", Object)], d.prototype, "hasActions", void 0), d = t([c("u-empty-state")], d);
|
|
57
66
|
//#endregion
|
|
58
67
|
export { d as EmptyState };
|
package/dist/index.d.ts
CHANGED
|
@@ -3,6 +3,8 @@ export type * from './types/AppConfigs';
|
|
|
3
3
|
export type * from './types/AppOptions';
|
|
4
4
|
export type * from './types/AuthConfig';
|
|
5
5
|
export type { SidebarPermissionGuard } from './layouts/SidebarPermission';
|
|
6
|
+
export type { RouteConfig, RouteContext, FallbackRouteConfig } from '@iyulab/router';
|
|
7
|
+
export { RouteError, NotFoundError, AccessDeniedError, ContentLoadError, ContentRenderError, OutletMissingError, } from '@iyulab/router';
|
|
6
8
|
export type { SidebarIconsConfig, SidebarLayoutConfig, SidebarItem, SidebarLogoConfig, SidebarLinkConfig, SidebarSectionConfig, SidebarGroupConfig, SidebarButtonConfig, SidebarMenuConfig, SidebarMenuItemConfig, SidebarHtmlConfig, SidebarState, } from './layouts/SidebarLayout.types';
|
|
7
9
|
export type { SidebarLayoutEventMap } from './layouts/SidebarLayout.js';
|
|
8
10
|
export { filterSidebarItems } from './layouts/filterSidebarItems.js';
|
package/dist/index.js
CHANGED
|
@@ -13,7 +13,8 @@ import { ActionBar as g } from "./components/ActionBar.js";
|
|
|
13
13
|
import { MasterDetailLayout as _ } from "./components/MasterDetailLayout.js";
|
|
14
14
|
import { Wizard as v } from "./components/Wizard.js";
|
|
15
15
|
import { translate as y } from "./translate.js";
|
|
16
|
+
import { AccessDeniedError as b, ContentLoadError as x, ContentRenderError as S, NotFoundError as C, OutletMissingError as w, RouteError as T } from "@iyulab/router";
|
|
16
17
|
//#region src/index.ts
|
|
17
|
-
var
|
|
18
|
+
var E = t;
|
|
18
19
|
//#endregion
|
|
19
|
-
export { g as ActionBar, p as EmptyState, l as GroupBox, d as InfoField, u as InfoSection, _ as MasterDetailLayout, c as PageHeader, e as ScreenObserver, h as Timeline, m as TimelineItem, v as Wizard, t as app,
|
|
20
|
+
export { b as AccessDeniedError, g as ActionBar, x as ContentLoadError, S as ContentRenderError, p as EmptyState, l as GroupBox, d as InfoField, u as InfoSection, _ as MasterDetailLayout, C as NotFoundError, w as OutletMissingError, c as PageHeader, T as RouteError, e as ScreenObserver, h as Timeline, m as TimelineItem, v as Wizard, t as app, E as default, n as filterSidebarItems, r as getDefaultLocale, i as getLocaleStrings, f as isBlank, a as modernAppLocale, o as registerLocale, s as setDefaultLocale, y as translate };
|
|
@@ -10,6 +10,9 @@ export interface ModernAppLocaleStrings {
|
|
|
10
10
|
/** Empty state — the list could not be loaded (`variant="error"`). */
|
|
11
11
|
errorTitle: string;
|
|
12
12
|
errorDescription: string;
|
|
13
|
+
/** Empty state — the screen exists but this user may not see it (`variant="no-access"` — a route guard said no). */
|
|
14
|
+
noAccessTitle: string;
|
|
15
|
+
noAccessDescription: string;
|
|
13
16
|
/** Accessible label for an overlay-mode close button (master-detail layout, sidebar layout overlay). */
|
|
14
17
|
detailClose: string;
|
|
15
18
|
/** Sidebar layout — accessible label for the mobile-header menu toggle button. */
|
package/dist/internals/locale.js
CHANGED
|
@@ -8,6 +8,8 @@ var t = {
|
|
|
8
8
|
noResultsDescription: "Try changing your search terms or filters.",
|
|
9
9
|
errorTitle: "Couldn’t load this list",
|
|
10
10
|
errorDescription: "Something went wrong while loading. Try again.",
|
|
11
|
+
noAccessTitle: "You don’t have access",
|
|
12
|
+
noAccessDescription: "Ask an administrator for access if you need it.",
|
|
11
13
|
detailClose: "Close",
|
|
12
14
|
toggleMobileMenu: "Toggle menu",
|
|
13
15
|
toggleSidebar: "Toggle sidebar",
|
|
@@ -26,6 +28,8 @@ n.register("en", t), n.register("ko", {
|
|
|
26
28
|
noResultsDescription: "검색어나 필터를 바꿔 보세요.",
|
|
27
29
|
errorTitle: "목록을 불러오지 못했습니다",
|
|
28
30
|
errorDescription: "불러오는 중 문제가 생겼습니다. 다시 시도해 주세요.",
|
|
31
|
+
noAccessTitle: "접근 권한이 없습니다",
|
|
32
|
+
noAccessDescription: "필요하면 관리자에게 권한을 요청하세요.",
|
|
29
33
|
detailClose: "닫기",
|
|
30
34
|
toggleMobileMenu: "메뉴 열고 닫기",
|
|
31
35
|
toggleSidebar: "사이드바 열고 닫기",
|
|
@@ -62,6 +66,8 @@ function l(e) {
|
|
|
62
66
|
noResultsDescription: t("noResultsDescription"),
|
|
63
67
|
errorTitle: t("errorTitle"),
|
|
64
68
|
errorDescription: t("errorDescription"),
|
|
69
|
+
noAccessTitle: t("noAccessTitle"),
|
|
70
|
+
noAccessDescription: t("noAccessDescription"),
|
|
65
71
|
detailClose: t("detailClose"),
|
|
66
72
|
toggleMobileMenu: t("toggleMobileMenu"),
|
|
67
73
|
toggleSidebar: t("toggleSidebar"),
|
package/dist/react.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { EventName } from '@lit/react';
|
|
|
3
3
|
import { SidebarLayout as SidebarLayoutElement, SidebarLayoutEventMap } from './layouts/SidebarLayout.js';
|
|
4
4
|
import { Wizard as WizardElement, WizardEventMap } from './components/Wizard.js';
|
|
5
5
|
import { GroupBoxLevel } from './components/GroupBox.js';
|
|
6
|
+
import { EmptyStateVariant } from './components/EmptyState.js';
|
|
6
7
|
import { TimelineItemColor } from './components/TimelineItem.js';
|
|
7
8
|
import { InfoFieldFormat, InfoFieldSize, InfoFieldTrend, InfoFieldTone } from './components/InfoField.js';
|
|
8
9
|
export { ScreenObserver } from './internals/ScreenObserver.js';
|
|
@@ -37,7 +38,8 @@ declare module 'react' {
|
|
|
37
38
|
sticky?: boolean;
|
|
38
39
|
};
|
|
39
40
|
'u-empty-state': React.DetailedHTMLProps<React.HTMLAttributes<HTMLElement>, HTMLElement> & {
|
|
40
|
-
|
|
41
|
+
/** 요소의 유니온을 그대로 쓴다 — 손으로 옮긴 유니온이 `'error'` 를 빠뜨린 채 남아 있었다. */
|
|
42
|
+
variant?: EmptyStateVariant;
|
|
41
43
|
title?: string;
|
|
42
44
|
description?: string;
|
|
43
45
|
locale?: string;
|
package/package.json
CHANGED
|
@@ -190,8 +190,11 @@ interface FallbackRouteConfig {
|
|
|
190
190
|
}
|
|
191
191
|
```
|
|
192
192
|
|
|
193
|
-
`RouteError`
|
|
194
|
-
|
|
193
|
+
`RouteError` (`code`/`original`/`timestamp` alongside the inherited `message`) is always populated
|
|
194
|
+
when the fallback fires. It and its subclasses (`AccessDeniedError` — an `enter` guard returned `false`,
|
|
195
|
+
code 403 · `NotFoundError` · `ContentLoadError` · `ContentRenderError` · `OutletMissingError`) are
|
|
196
|
+
exported from `@iyulab/modern-app` — tell a blocked screen from a missing one with
|
|
197
|
+
`ctx.error instanceof AccessDeniedError` and render it as `<u-empty-state variant="no-access">`.
|
|
195
198
|
|
|
196
199
|
---
|
|
197
200
|
|
|
@@ -8,6 +8,8 @@ Shown when a list or a search comes back with nothing.
|
|
|
8
8
|
*nothing has been created yet* and the next action is **create**. The second means *nothing
|
|
9
9
|
matches these conditions* and the next action is **change the filter**. Show the same wording
|
|
10
10
|
for both and a user with a filter still applied reads it as *"my data disappeared"*.
|
|
11
|
+
The same holds for a screen this user may not see: it is neither empty nor broken, and its next
|
|
12
|
+
action is **asking for access**.
|
|
11
13
|
|
|
12
14
|
```html
|
|
13
15
|
<u-empty-state variant="no-data" title="No orders yet">
|
|
@@ -20,6 +22,9 @@ for both and a user with a filter still applied reads it as *"my data disappeare
|
|
|
20
22
|
<u-empty-state variant="error" description="Request failed (503)">
|
|
21
23
|
<u-button slot="actions">Try again</u-button>
|
|
22
24
|
</u-empty-state>
|
|
25
|
+
|
|
26
|
+
<!-- A route guard said no (403) — not an outage, so it is not announced as an alert. -->
|
|
27
|
+
<u-empty-state variant="no-access"></u-empty-state>
|
|
23
28
|
```
|
|
24
29
|
|
|
25
30
|
## Slots
|
|
@@ -33,7 +38,7 @@ for both and a user with a filter still applied reads it as *"my data disappeare
|
|
|
33
38
|
|
|
34
39
|
| Property | Type | Default | Reflect | Description |
|
|
35
40
|
|----------|------|---------|---------|-------------|
|
|
36
|
-
| `variant` | `'no-data'\|'no-results'\|'error'` | `'no-data'` | ✓ | Which fact is being shown; changes the default wording. `error` (could not load) announces its title |
|
|
41
|
+
| `variant` | `'no-data'\|'no-results'\|'error'\|'no-access'` | `'no-data'` | ✓ | Which fact is being shown; changes the default wording. `error` (could not load) announces its title; `no-access` (this user may not see it) does not |
|
|
37
42
|
| `title` | `string` | `''` | | Override the default title |
|
|
38
43
|
| `description` | `string` | `''` | | Override the default description |
|
|
39
44
|
| `locale` | `string` | `''` | | Locale tag override for built-in strings |
|
|
@@ -63,3 +68,23 @@ for both and a user with a filter still applied reads it as *"my data disappeare
|
|
|
63
68
|
⚠ Default wording is **English**, with Korean built in — choose the language with `Locale.set()` of
|
|
64
69
|
`@iyulab/components`, register others with `modernAppLocale.register(lang, …)`, or pass
|
|
65
70
|
`title`/`description` per screen.
|
|
71
|
+
|
|
72
|
+
## Blocked routes
|
|
73
|
+
|
|
74
|
+
A route `enter` guard that returns `false` raises `AccessDeniedError` (code 403) into the app
|
|
75
|
+
`fallback`. The error classes are exported from `@iyulab/modern-app`, so the fallback can tell a
|
|
76
|
+
blocked screen from a missing one without depending on the router directly:
|
|
77
|
+
|
|
78
|
+
```typescript
|
|
79
|
+
import { app, AccessDeniedError } from '@iyulab/modern-app';
|
|
80
|
+
import { html } from 'lit';
|
|
81
|
+
|
|
82
|
+
await app.load({
|
|
83
|
+
// …
|
|
84
|
+
fallback: {
|
|
85
|
+
render: (ctx) => ctx.error instanceof AccessDeniedError
|
|
86
|
+
? html`<u-empty-state variant="no-access"></u-empty-state>`
|
|
87
|
+
: html`<u-empty-state variant="error" .description=${ctx.error.message}></u-empty-state>`,
|
|
88
|
+
},
|
|
89
|
+
});
|
|
90
|
+
```
|