@operato/menu 10.6.0 → 10.8.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 +56 -0
- package/dist/src/index.d.ts +18 -0
- package/dist/src/index.js +4 -1
- package/dist/src/index.js.map +1 -1
- package/dist/src/menu-active.d.ts +89 -0
- package/dist/src/menu-active.js +115 -0
- package/dist/src/menu-active.js.map +1 -0
- package/dist/src/menu-mode.d.ts +98 -0
- package/dist/src/menu-mode.js +100 -0
- package/dist/src/menu-mode.js.map +1 -0
- package/dist/src/menu-portrait-styles.d.ts +27 -0
- package/dist/src/menu-portrait-styles.js +516 -92
- package/dist/src/menu-portrait-styles.js.map +1 -1
- package/dist/src/menu-privilege.d.ts +34 -0
- package/dist/src/menu-privilege.js +22 -0
- package/dist/src/menu-privilege.js.map +1 -0
- package/dist/src/ox-menu-part.d.ts +103 -0
- package/dist/src/ox-menu-part.js +224 -23
- package/dist/src/ox-menu-part.js.map +1 -1
- package/dist/src/ox-menu-portrait.d.ts +158 -3
- package/dist/src/ox-menu-portrait.js +511 -13
- package/dist/src/ox-menu-portrait.js.map +1 -1
- package/dist/src/types.d.ts +38 -1
- package/dist/src/types.js.map +1 -1
- package/dist/test/menu-active.test.d.ts +1 -0
- package/dist/test/menu-active.test.js +131 -0
- package/dist/test/menu-active.test.js.map +1 -0
- package/dist/test/menu-mode.test.d.ts +1 -0
- package/dist/test/menu-mode.test.js +108 -0
- package/dist/test/menu-mode.test.js.map +1 -0
- package/dist/test/menu-privilege.test.d.ts +1 -0
- package/dist/test/menu-privilege.test.js +94 -0
- package/dist/test/menu-privilege.test.js.map +1 -0
- package/dist/test/ox-menu-portrait.test.d.ts +1 -0
- package/dist/test/ox-menu-portrait.test.js +339 -0
- package/dist/test/ox-menu-portrait.test.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +10 -10
- package/test/menu-active.test.ts +162 -0
- package/test/menu-mode.test.ts +135 -0
- package/test/menu-privilege.test.ts +138 -0
- package/test/ox-menu-portrait.test.ts +431 -0
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,62 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
+
## [10.8.0](https://github.com/hatiolab/operato/compare/v10.7.0...v10.8.0) (2026-09-04)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
### :sparkles: Styling
|
|
10
|
+
|
|
11
|
+
* **menu:** 노브를 10px · 팝업과 같은 선 색 · 그림자 없음으로 ([b015abd](https://github.com/hatiolab/operato/commit/b015abda3cd10950e8266d00a45ad922fb731219))
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
### :house: Code Refactoring
|
|
15
|
+
|
|
16
|
+
* **menu:** 깨지기 쉬운 규칙을 순수 함수로 꺼내고 시험으로 못박는다 ([97bdfe8](https://github.com/hatiolab/operato/commit/97bdfe8710bb66ca791d7e7c8014b7598ea04e6c))
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
### :bug: Bug Fix
|
|
20
|
+
|
|
21
|
+
* **menu:** 노브가 팝업 안에서 잘려 안 보이던 것을 고친다 ([6216d7a](https://github.com/hatiolab/operato/commit/6216d7aa017931180b414b85c5bb9361959d18e0))
|
|
22
|
+
* **menu:** 메뉴 감추기를 켜면 「관리」 줄이 사라지던 것을 고친다 ([d4e87e1](https://github.com/hatiolab/operato/commit/d4e87e1b602c98e97303ee719adc29c1903298d0))
|
|
23
|
+
* **menu:** 서랍에서 최상위는 펼치기만 · 화살표로 다른 묶음도 열린다 ([3f29730](https://github.com/hatiolab/operato/commit/3f297307109c201493de16ed7b6542ed5e3c802c))
|
|
24
|
+
* **menu:** 시스템 줄도 하나만 열리는 규칙을 함께 쓴다 ([cf0d4b9](https://github.com/hatiolab/operato/commit/cf0d4b98a3437d57adfd945ef63bd714f2a386ea))
|
|
25
|
+
* **menu:** 열린 최상위 메뉴가 접히지 않던 것을 고친다 — 세 겹이었다 ([5735904](https://github.com/hatiolab/operato/commit/5735904e0eb0ee685f2f89651e8c353937a3ab2a))
|
|
26
|
+
* **menu:** 접힌 레일에서 배지 숫자가 아이콘에 묻히던 것을 고친다 ([074fca4](https://github.com/hatiolab/operato/commit/074fca49840a96a0e824807e7466861e4bab9ca3))
|
|
27
|
+
* **menu:** 접힌 레일에서 폭 손잡이를 감춘다 ([553b022](https://github.com/hatiolab/operato/commit/553b022870dbda70b2bf73f777cbddf35ced2dc1))
|
|
28
|
+
* **menu:** 접힌 레일의 팝업에 이름이 안 나오던 것을 고친다 ([c0cc3d4](https://github.com/hatiolab/operato/commit/c0cc3d4c55a9b8c1364ac843f5ec3c05bfb9bf8f))
|
|
29
|
+
* **menu:** 접힌 레일의 팝업을 레일에서 떼어 놓는다 ([efde36f](https://github.com/hatiolab/operato/commit/efde36fbdce7d8c51cf4c24acfc72c86a8adb4ff))
|
|
30
|
+
* **menu:** 줄 높이가 창 높이를 따라 달라지던 것을 막고 밀도를 조정 지점으로 연다 ([a85b579](https://github.com/hatiolab/operato/commit/a85b57900532d8a4e38797632b8e26d18ae87f61))
|
|
31
|
+
* **menu:** 최상위와 겹치는 하위가 표시를 못 받던 것을 고친다 ([181ce5c](https://github.com/hatiolab/operato/commit/181ce5c554893bd25577e61d65cdae0e58953810))
|
|
32
|
+
* **menu:** 팝업 줄이 건수를 빠뜨리던 것을 고친다 ([6a7c16d](https://github.com/hatiolab/operato/commit/6a7c16d352e24c11ce9cc86f59534fd4dd7f8c72))
|
|
33
|
+
* **menu:** 팝업 틈을 16px 에서 8px 로 ([babec07](https://github.com/hatiolab/operato/commit/babec0755819e142cf6c99a99891c2fcc37a2469))
|
|
34
|
+
* **menu:** 팝업에서 지금 페이지가 표시되지 않던 것을 고친다 ([e2feec1](https://github.com/hatiolab/operato/commit/e2feec191c692a05adc324454340644babbac7a1))
|
|
35
|
+
* **menu:** 팝업이 화면 밖으로 나가거나 괜히 스크롤되던 것을 고친다 ([b5f3a0a](https://github.com/hatiolab/operato/commit/b5f3a0a7b28aa2b772089c35e88669c7142faaef))
|
|
36
|
+
* **menu:** 하위가 펼쳐져 있으면 최상위의 합계를 감춘다 ([c329268](https://github.com/hatiolab/operato/commit/c329268d54dcc3a9b77e75b49b1c2134a1e55b76))
|
|
37
|
+
* **menu:** 합계 배지 감추기를 레일 모드 밖에서도 되게 한다 ([2acdfea](https://github.com/hatiolab/operato/commit/2acdfea2b4364e10c1024ca4401ba46b43a9471a))
|
|
38
|
+
* **menu:** 화살표를 되살리고 접힘 규칙이 팝업까지 흘러가던 것을 막는다 ([b6cba38](https://github.com/hatiolab/operato/commit/b6cba38efba8e5ef3bf18553b5d6a185b495380a))
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
### :rocket: New Features
|
|
42
|
+
|
|
43
|
+
* **menu:** 건수 배지가 제품의 강조색을 쓴다 ([e842660](https://github.com/hatiolab/operato/commit/e84266053b77bc7ba5d93be020e49ff07398c898))
|
|
44
|
+
* **menu:** 공개면을 열고 한 줄 설명을 툴팁으로 낸다 ([c1f85e2](https://github.com/hatiolab/operato/commit/c1f85e2c2b13bc5bf5b70034f5342fd2e97830cd))
|
|
45
|
+
* **menu:** 메뉴가 자기 문을 선언한다 — GraphQL 지시자와 같은 낱말로 ([442f93d](https://github.com/hatiolab/operato/commit/442f93d31ddc9f74a48758372bfff5191faefd1a))
|
|
46
|
+
* **menu:** 손잡이로 좁게 끌면 스스로 접힌다 · 모양을 한 벌로 통일한다 ([651a47a](https://github.com/hatiolab/operato/commit/651a47af6e8c401f97104e5a2bebb79097e61199))
|
|
47
|
+
* **menu:** 시스템 항목을 레일 바닥에 붙인다 ([c5ba37e](https://github.com/hatiolab/operato/commit/c5ba37ea93c3c8c409593562801e730ada1cb3f6))
|
|
48
|
+
* **menu:** 왼쪽 메뉴에 레일 모드 — 접히고, 접히면 하위가 팝업으로 뜬다 ([00f30f0](https://github.com/hatiolab/operato/commit/00f30f08c4c1f23c0200b0062f80c2212c0f5a43))
|
|
49
|
+
* **menu:** 팝업이 어느 최상위에서 열렸는지 보이게 한다 ([c47ccfb](https://github.com/hatiolab/operato/commit/c47ccfbd286afa9d33aa37147030954e3e08445d))
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
## [10.7.0](https://github.com/hatiolab/operato/compare/v10.6.0...v10.7.0) (2026-09-02)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
### :bug: Bug Fix
|
|
57
|
+
|
|
58
|
+
* **menu:** 주소가 화면 안의 자리까지 늘어나면 메뉴가 그 화면을 못 알아봤다 ([8325c47](https://github.com/hatiolab/operato/commit/8325c47f4a0b6fdaba7bce5d58ccff2b41cc2141))
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
|
|
6
62
|
## [10.6.0](https://github.com/hatiolab/operato/compare/v10.5.0...v10.6.0) (2026-09-02)
|
|
7
63
|
|
|
8
64
|
**Note:** Version bump only for package @operato/menu
|
package/dist/src/index.d.ts
CHANGED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 이 꾸러미의 **공개면**.
|
|
3
|
+
*
|
|
4
|
+
* 여기가 비어 있었다. 그래서 메뉴를 쓰는 앱이 `Menu` 를 자기 쪽에 다시 적거나 깊은 경로로
|
|
5
|
+
* 가져와야 했다 — 둘 다 이 꾸러미가 모양을 바꿀 때 조용히 어긋난다.
|
|
6
|
+
*
|
|
7
|
+
* **부품은 여기서 내보내지 않는다.** 부품을 가져오면 커스텀 엘리먼트가 등록되는 부수효과가 있어서,
|
|
8
|
+
* 타입 하나 쓰려던 앱이 부품까지 싣게 된다. 부품은 각자의 서브경로로 가져간다.
|
|
9
|
+
*
|
|
10
|
+
* ```
|
|
11
|
+
* import '@operato/menu/ox-menu-portrait.js' 부품 — 등록까지 한다
|
|
12
|
+
* import type { Menu } from '@operato/menu' 모양 — 싣는 것이 없다
|
|
13
|
+
* ```
|
|
14
|
+
*/
|
|
15
|
+
export type { Menu } from './types.js';
|
|
16
|
+
export * from './menu-mode.js';
|
|
17
|
+
export * from './menu-active.js';
|
|
18
|
+
export * from './menu-privilege.js';
|
package/dist/src/index.js
CHANGED
package/dist/src/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"","sourcesContent":[""]}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAgBA,qDAAqD;AACrD,cAAc,gBAAgB,CAAA;AAC9B,cAAc,kBAAkB,CAAA;AAChC,cAAc,qBAAqB,CAAA","sourcesContent":["/**\n * 이 꾸러미의 **공개면**.\n *\n * 여기가 비어 있었다. 그래서 메뉴를 쓰는 앱이 `Menu` 를 자기 쪽에 다시 적거나 깊은 경로로\n * 가져와야 했다 — 둘 다 이 꾸러미가 모양을 바꿀 때 조용히 어긋난다.\n *\n * **부품은 여기서 내보내지 않는다.** 부품을 가져오면 커스텀 엘리먼트가 등록되는 부수효과가 있어서,\n * 타입 하나 쓰려던 앱이 부품까지 싣게 된다. 부품은 각자의 서브경로로 가져간다.\n *\n * ```\n * import '@operato/menu/ox-menu-portrait.js' 부품 — 등록까지 한다\n * import type { Menu } from '@operato/menu' 모양 — 싣는 것이 없다\n * ```\n */\nexport type { Menu } from './types.js'\n\n/* 모양 표와 활성 메뉴 판정 — 순수 함수라 앱이 같은 판단을 다시 짜지 않아도 된다. */\nexport * from './menu-mode.js'\nexport * from './menu-active.js'\nexport * from './menu-privilege.js'\n"]}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { Menu } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* 지금 보고 있는 자리에 해당하는 메뉴를 찾는다.
|
|
4
|
+
*
|
|
5
|
+
* ## 왜 순수 함수로 꺼냈나
|
|
6
|
+
*
|
|
7
|
+
* 이 규칙이 컴포넌트의 `willUpdate` 안에 있었고, 두 번 조용히 깨졌다.
|
|
8
|
+
*
|
|
9
|
+
* ```
|
|
10
|
+
* 최상위가 하위의 표시를 삼켰다 최상위마다 하위 하나가 표시를 못 받았다
|
|
11
|
+
* 손으로 여닫은 것이 되돌려졌다 매 렌더마다 주소로 다시 계산되니까
|
|
12
|
+
* ```
|
|
13
|
+
*
|
|
14
|
+
* 둘 다 화면을 눈으로 보고서야 알았다. 순수 함수면 시험으로 못박을 수 있다
|
|
15
|
+
* (§`test/menu-active.test.ts`).
|
|
16
|
+
*
|
|
17
|
+
* ## 두 값을 낸다
|
|
18
|
+
*
|
|
19
|
+
* ```
|
|
20
|
+
* top 하위가 펼쳐질 묶음
|
|
21
|
+
* menu 지금 보고 있는 그 줄 — 강조를 받는 자리
|
|
22
|
+
* ```
|
|
23
|
+
*
|
|
24
|
+
* 최상위가 그 자리일 수도 있고(하위가 없는 묶음) 하위가 그 자리일 수도 있다.
|
|
25
|
+
*/
|
|
26
|
+
export type ActiveMenus = {
|
|
27
|
+
top?: Menu;
|
|
28
|
+
menu?: Menu;
|
|
29
|
+
};
|
|
30
|
+
/** 지금 어디를 보고 있나. 라우터가 주는 것을 그대로 받는다. */
|
|
31
|
+
export type Where = {
|
|
32
|
+
/** 화면 이름. 라우터가 주소를 두 토막으로 자른 앞쪽이다. */
|
|
33
|
+
page: string;
|
|
34
|
+
/** 그 화면 안의 자리. 없을 수 있다. */
|
|
35
|
+
resourceId?: string;
|
|
36
|
+
/** 화면이 스스로 「나는 이 메뉴에 딸렸다」고 말한 것. */
|
|
37
|
+
context?: {
|
|
38
|
+
menu?: string;
|
|
39
|
+
};
|
|
40
|
+
};
|
|
41
|
+
/**
|
|
42
|
+
* 사용자가 손으로 정한 펼침.
|
|
43
|
+
*
|
|
44
|
+
* ```
|
|
45
|
+
* undefined 정한 적 없다 — 주소가 가리키는 것을 쓴다
|
|
46
|
+
* null 닫았다
|
|
47
|
+
* Menu 그 묶음을 열었다
|
|
48
|
+
* ```
|
|
49
|
+
*
|
|
50
|
+
* 두 값(열었다·닫았다)을 다 담아야 한다. 한때 「닫았다」만 기억했는데, 그러면 화살표로 다른
|
|
51
|
+
* 묶음을 열 수 없었다 — 그 방향도 주소가 되돌린다.
|
|
52
|
+
*/
|
|
53
|
+
export type UserOpened = Menu | null | undefined;
|
|
54
|
+
/**
|
|
55
|
+
* 주소를 한 문자열로 잇는다.
|
|
56
|
+
*
|
|
57
|
+
* 라우터는 주소를 두 토막으로 자른다. 메뉴는 화면을 가리키지만 주소는 그 화면 안의 자리까지
|
|
58
|
+
* 담을 수 있다.
|
|
59
|
+
*
|
|
60
|
+
* ```
|
|
61
|
+
* /approval-pending-list/reference 결재함의 참조 묶음
|
|
62
|
+
* /quality-list/disposition 품질 화면의 부적합 처분
|
|
63
|
+
* ```
|
|
64
|
+
*/
|
|
65
|
+
export declare function joinedPath(where: Where): string;
|
|
66
|
+
/**
|
|
67
|
+
* 주소가 가리키는 메뉴.
|
|
68
|
+
*
|
|
69
|
+
* ── 여러 번 훑는 이유 — 순서가 뜻을 갖는다 ──────────────────────────────────
|
|
70
|
+
* `a` 와 `a/b` 두 메뉴가 함께 있고 주소가 `a/b` 이면 **`a/b` 가 이겨야 한다.** 한 번에 훑으면서
|
|
71
|
+
* 조건을 OR 로 묶으면 목록에서 먼저 나온 `a` 가 이기고, 실제로 보고 있는 자리를 가리키는 메뉴가
|
|
72
|
+
* 표시를 못 받는다.
|
|
73
|
+
*
|
|
74
|
+
* 분명한 것부터 본다 — 바로 그 자리 · 화면이 말한 것 · 같은 화면 · 앱이 준 판정.
|
|
75
|
+
*/
|
|
76
|
+
export declare function menuForRoute(menus: Menu[], where: Where): ActiveMenus;
|
|
77
|
+
/**
|
|
78
|
+
* 화면에 그릴 두 값을 정한다.
|
|
79
|
+
*
|
|
80
|
+
* ── 무엇이 무엇을 이기나 ────────────────────────────────────────────────────
|
|
81
|
+
* ```
|
|
82
|
+
* top 손으로 정한 것이 있으면 그것. 없으면 주소가 가리키는 것
|
|
83
|
+
* menu 늘 주소에서 온다
|
|
84
|
+
* ```
|
|
85
|
+
*
|
|
86
|
+
* `menu` 를 손으로 정한 것에 맡기지 않는 이유는, **다른 묶음을 열어 놓고 둘러보는 동안에도
|
|
87
|
+
* 자기가 어느 화면에 있는지는 알아야** 하기 때문이다.
|
|
88
|
+
*/
|
|
89
|
+
export declare function resolveActive(menus: Menu[], where: Where, userOpened: UserOpened): ActiveMenus;
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/** 메뉴가 선언한 경로. 쿼리는 떼고 본다. */
|
|
2
|
+
function declaredPath(menu) {
|
|
3
|
+
var _a;
|
|
4
|
+
return (_a = menu.path) === null || _a === void 0 ? void 0 : _a.split('?')[0];
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* 주소를 한 문자열로 잇는다.
|
|
8
|
+
*
|
|
9
|
+
* 라우터는 주소를 두 토막으로 자른다. 메뉴는 화면을 가리키지만 주소는 그 화면 안의 자리까지
|
|
10
|
+
* 담을 수 있다.
|
|
11
|
+
*
|
|
12
|
+
* ```
|
|
13
|
+
* /approval-pending-list/reference 결재함의 참조 묶음
|
|
14
|
+
* /quality-list/disposition 품질 화면의 부적합 처분
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
17
|
+
export function joinedPath(where) {
|
|
18
|
+
return where.resourceId ? `${where.page}/${where.resourceId}` : where.page;
|
|
19
|
+
}
|
|
20
|
+
/** 메뉴가 **바로 그 자리**를 가리키는가. 주소가 `a/b` 이고 메뉴도 `a/b` 인 경우다. */
|
|
21
|
+
function pointsAtSamePlace(menu, path) {
|
|
22
|
+
return declaredPath(menu) === path;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* 메뉴가 **같은 화면**을 가리키는가.
|
|
26
|
+
*
|
|
27
|
+
* 메뉴가 `a/b` 를 선언했다면 여기서는 안 맞는다(`a/b` ≠ `a`). 그 경우는 위의 자리 비교가 잡는다.
|
|
28
|
+
*/
|
|
29
|
+
function pointsAtSamePage(menu, page) {
|
|
30
|
+
return declaredPath(menu) === page;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* 화면이 스스로 「나는 이 메뉴에 딸렸다」고 말하는가.
|
|
34
|
+
*
|
|
35
|
+
* 목록에서 줄을 눌러 들어가는 상세 화면은 메뉴에 아예 없다. 그러면 왼쪽 메뉴에 아무 표시가 없고,
|
|
36
|
+
* 들어간 사람이 자기가 어디에 있는지 잃는다.
|
|
37
|
+
*
|
|
38
|
+
* 어느 목록에서 열리는지는 **그 화면이 가장 잘 안다** — 메뉴 쪽에 상세 화면 목록을 나열해 두면
|
|
39
|
+
* 화면이 늘 때마다 그 목록을 같이 고쳐야 하고, 빠뜨린 화면만 조용히 표시를 잃는다.
|
|
40
|
+
*/
|
|
41
|
+
function pageSaysItBelongsHere(menu, context) {
|
|
42
|
+
const belongsTo = context === null || context === void 0 ? void 0 : context.menu;
|
|
43
|
+
return !!belongsTo && declaredPath(menu) === belongsTo;
|
|
44
|
+
}
|
|
45
|
+
/** 앱이 직접 판정을 주는 경우. 선언한 경로보다 뒤에 본다 — 적어 둔 경로가 더 분명한 뜻이다. */
|
|
46
|
+
function saysItIsActive(menu, path, page) {
|
|
47
|
+
return !!(menu.active && typeof menu.active === 'function' && menu.active.call(menu, { path, page }));
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* 최상위와 그 안의 메뉴를 훑어 처음 맞는 것을 찾는다.
|
|
51
|
+
*
|
|
52
|
+
* ── 하위를 먼저 본다 ────────────────────────────────────────────────────────
|
|
53
|
+
* **최상위의 주소는 대개 자기 하위 하나의 주소다.** 그 최상위를 눌렀을 때 갈 곳이 필요하고,
|
|
54
|
+
* 그 자리를 하위 하나가 맡는다.
|
|
55
|
+
*
|
|
56
|
+
* ```
|
|
57
|
+
* { name: 'my-work', path: 'todo-list', menus: [{ name: 'todo', path: 'todo-list' }, …] }
|
|
58
|
+
* ```
|
|
59
|
+
*
|
|
60
|
+
* 최상위를 먼저 보면 주소 `todo-list` 에서 **최상위가 매치를 삼킨다.** 그러면 「할 일」이 표시를
|
|
61
|
+
* 못 받고, 최상위마다 하위 하나가 그렇게 된다.
|
|
62
|
+
*
|
|
63
|
+
* 하위가 **더 구체적인 답**이다. 최상위는 묶음이고 그 주소는 하위로 가는 지름길이다.
|
|
64
|
+
*/
|
|
65
|
+
function lookup(menus, matches) {
|
|
66
|
+
var _a;
|
|
67
|
+
for (const top of menus) {
|
|
68
|
+
const inner = (_a = top.menus) === null || _a === void 0 ? void 0 : _a.find(matches);
|
|
69
|
+
if (inner) {
|
|
70
|
+
return { top, menu: inner };
|
|
71
|
+
}
|
|
72
|
+
if (matches(top)) {
|
|
73
|
+
return { top, menu: top };
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return undefined;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* 주소가 가리키는 메뉴.
|
|
80
|
+
*
|
|
81
|
+
* ── 여러 번 훑는 이유 — 순서가 뜻을 갖는다 ──────────────────────────────────
|
|
82
|
+
* `a` 와 `a/b` 두 메뉴가 함께 있고 주소가 `a/b` 이면 **`a/b` 가 이겨야 한다.** 한 번에 훑으면서
|
|
83
|
+
* 조건을 OR 로 묶으면 목록에서 먼저 나온 `a` 가 이기고, 실제로 보고 있는 자리를 가리키는 메뉴가
|
|
84
|
+
* 표시를 못 받는다.
|
|
85
|
+
*
|
|
86
|
+
* 분명한 것부터 본다 — 바로 그 자리 · 화면이 말한 것 · 같은 화면 · 앱이 준 판정.
|
|
87
|
+
*/
|
|
88
|
+
export function menuForRoute(menus, where) {
|
|
89
|
+
const path = joinedPath(where);
|
|
90
|
+
const page = where.page;
|
|
91
|
+
const found = lookup(menus, menu => pointsAtSamePlace(menu, path)) ||
|
|
92
|
+
lookup(menus, menu => pageSaysItBelongsHere(menu, where.context)) ||
|
|
93
|
+
lookup(menus, menu => pointsAtSamePage(menu, page) || saysItIsActive(menu, path, page));
|
|
94
|
+
return { top: found === null || found === void 0 ? void 0 : found.top, menu: (found === null || found === void 0 ? void 0 : found.menu) || (found === null || found === void 0 ? void 0 : found.top) };
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* 화면에 그릴 두 값을 정한다.
|
|
98
|
+
*
|
|
99
|
+
* ── 무엇이 무엇을 이기나 ────────────────────────────────────────────────────
|
|
100
|
+
* ```
|
|
101
|
+
* top 손으로 정한 것이 있으면 그것. 없으면 주소가 가리키는 것
|
|
102
|
+
* menu 늘 주소에서 온다
|
|
103
|
+
* ```
|
|
104
|
+
*
|
|
105
|
+
* `menu` 를 손으로 정한 것에 맡기지 않는 이유는, **다른 묶음을 열어 놓고 둘러보는 동안에도
|
|
106
|
+
* 자기가 어느 화면에 있는지는 알아야** 하기 때문이다.
|
|
107
|
+
*/
|
|
108
|
+
export function resolveActive(menus, where, userOpened) {
|
|
109
|
+
const byRoute = menuForRoute(menus, where);
|
|
110
|
+
return {
|
|
111
|
+
top: userOpened === undefined ? byRoute.top : (userOpened !== null && userOpened !== void 0 ? userOpened : undefined),
|
|
112
|
+
menu: byRoute.menu
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
//# sourceMappingURL=menu-active.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"menu-active.js","sourceRoot":"","sources":["../../src/menu-active.ts"],"names":[],"mappings":"AAoDA,6BAA6B;AAC7B,SAAS,YAAY,CAAC,IAAU;;IAC9B,OAAO,MAAA,IAAI,CAAC,IAAI,0CAAE,KAAK,CAAC,GAAG,EAAE,CAAC,CAAC,CAAA;AACjC,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,UAAU,CAAC,KAAY;IACrC,OAAO,KAAK,CAAC,UAAU,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,IAAI,IAAI,KAAK,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAA;AAC5E,CAAC;AAED,4DAA4D;AAC5D,SAAS,iBAAiB,CAAC,IAAU,EAAE,IAAY;IACjD,OAAO,YAAY,CAAC,IAAI,CAAC,KAAK,IAAI,CAAA;AACpC,CAAC;AAED;;;;GAIG;AACH,SAAS,gBAAgB,CAAC,IAAU,EAAE,IAAY;IAChD,OAAO,YAAY,CAAC,IAAI,CAAC,KAAK,IAAI,CAAA;AACpC,CAAC;AAED;;;;;;;;GAQG;AACH,SAAS,qBAAqB,CAAC,IAAU,EAAE,OAA2B;IACpE,MAAM,SAAS,GAAG,OAAO,aAAP,OAAO,uBAAP,OAAO,CAAE,IAAI,CAAA;IAC/B,OAAO,CAAC,CAAC,SAAS,IAAI,YAAY,CAAC,IAAI,CAAC,KAAK,SAAS,CAAA;AACxD,CAAC;AAED,4DAA4D;AAC5D,SAAS,cAAc,CAAC,IAAU,EAAE,IAAY,EAAE,IAAY;IAC5D,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,IAAI,OAAO,IAAI,CAAC,MAAM,KAAK,UAAU,IAAI,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,EAAE,IAAI,EAAE,IAAI,EAAS,CAAC,CAAC,CAAA;AAC9G,CAAC;AAED;;;;;;;;;;;;;;;GAeG;AACH,SAAS,MAAM,CAAC,KAAa,EAAE,OAAgC;;IAC7D,KAAK,MAAM,GAAG,IAAI,KAAK,EAAE,CAAC;QACxB,MAAM,KAAK,GAAG,MAAA,GAAG,CAAC,KAAK,0CAAE,IAAI,CAAC,OAAO,CAAC,CAAA;QACtC,IAAI,KAAK,EAAE,CAAC;YACV,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,KAAK,EAAE,CAAA;QAC7B,CAAC;QAED,IAAI,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC;YACjB,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,GAAG,EAAE,CAAA;QAC3B,CAAC;IACH,CAAC;IAED,OAAO,SAAS,CAAA;AAClB,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,YAAY,CAAC,KAAa,EAAE,KAAY;IACtD,MAAM,IAAI,GAAG,UAAU,CAAC,KAAK,CAAC,CAAA;IAC9B,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,CAAA;IAEvB,MAAM,KAAK,GACT,MAAM,CAAC,KAAK,EAAE,IAAI,CAAC,EAAE,CAAC,iBAAiB,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;QACpD,MAAM,CAAC,KAAK,EAAE,IAAI,CAAC,EAAE,CAAC,qBAAqB,CAAC,IAAI,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC;QACjE,MAAM,CAAC,KAAK,EAAE,IAAI,CAAC,EAAE,CAAC,gBAAgB,CAAC,IAAI,EAAE,IAAI,CAAC,IAAI,cAAc,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC,CAAA;IAEzF,OAAO,EAAE,GAAG,EAAE,KAAK,aAAL,KAAK,uBAAL,KAAK,CAAE,GAAG,EAAE,IAAI,EAAE,CAAA,KAAK,aAAL,KAAK,uBAAL,KAAK,CAAE,IAAI,MAAI,KAAK,aAAL,KAAK,uBAAL,KAAK,CAAE,GAAG,CAAA,EAAE,CAAA;AAC7D,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,aAAa,CAAC,KAAa,EAAE,KAAY,EAAE,UAAsB;IAC/E,MAAM,OAAO,GAAG,YAAY,CAAC,KAAK,EAAE,KAAK,CAAC,CAAA;IAE1C,OAAO;QACL,GAAG,EAAE,UAAU,KAAK,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,UAAU,aAAV,UAAU,cAAV,UAAU,GAAI,SAAS,CAAC;QACvE,IAAI,EAAE,OAAO,CAAC,IAAI;KACnB,CAAA;AACH,CAAC","sourcesContent":["import { Menu } from './types.js'\n\n/**\n * 지금 보고 있는 자리에 해당하는 메뉴를 찾는다.\n *\n * ## 왜 순수 함수로 꺼냈나\n *\n * 이 규칙이 컴포넌트의 `willUpdate` 안에 있었고, 두 번 조용히 깨졌다.\n *\n * ```\n * 최상위가 하위의 표시를 삼켰다 최상위마다 하위 하나가 표시를 못 받았다\n * 손으로 여닫은 것이 되돌려졌다 매 렌더마다 주소로 다시 계산되니까\n * ```\n *\n * 둘 다 화면을 눈으로 보고서야 알았다. 순수 함수면 시험으로 못박을 수 있다\n * (§`test/menu-active.test.ts`).\n *\n * ## 두 값을 낸다\n *\n * ```\n * top 하위가 펼쳐질 묶음\n * menu 지금 보고 있는 그 줄 — 강조를 받는 자리\n * ```\n *\n * 최상위가 그 자리일 수도 있고(하위가 없는 묶음) 하위가 그 자리일 수도 있다.\n */\nexport type ActiveMenus = { top?: Menu; menu?: Menu }\n\n/** 지금 어디를 보고 있나. 라우터가 주는 것을 그대로 받는다. */\nexport type Where = {\n /** 화면 이름. 라우터가 주소를 두 토막으로 자른 앞쪽이다. */\n page: string\n /** 그 화면 안의 자리. 없을 수 있다. */\n resourceId?: string\n /** 화면이 스스로 「나는 이 메뉴에 딸렸다」고 말한 것. */\n context?: { menu?: string }\n}\n\n/**\n * 사용자가 손으로 정한 펼침.\n *\n * ```\n * undefined 정한 적 없다 — 주소가 가리키는 것을 쓴다\n * null 닫았다\n * Menu 그 묶음을 열었다\n * ```\n *\n * 두 값(열었다·닫았다)을 다 담아야 한다. 한때 「닫았다」만 기억했는데, 그러면 화살표로 다른\n * 묶음을 열 수 없었다 — 그 방향도 주소가 되돌린다.\n */\nexport type UserOpened = Menu | null | undefined\n\n/** 메뉴가 선언한 경로. 쿼리는 떼고 본다. */\nfunction declaredPath(menu: Menu) {\n return menu.path?.split('?')[0]\n}\n\n/**\n * 주소를 한 문자열로 잇는다.\n *\n * 라우터는 주소를 두 토막으로 자른다. 메뉴는 화면을 가리키지만 주소는 그 화면 안의 자리까지\n * 담을 수 있다.\n *\n * ```\n * /approval-pending-list/reference 결재함의 참조 묶음\n * /quality-list/disposition 품질 화면의 부적합 처분\n * ```\n */\nexport function joinedPath(where: Where) {\n return where.resourceId ? `${where.page}/${where.resourceId}` : where.page\n}\n\n/** 메뉴가 **바로 그 자리**를 가리키는가. 주소가 `a/b` 이고 메뉴도 `a/b` 인 경우다. */\nfunction pointsAtSamePlace(menu: Menu, path: string) {\n return declaredPath(menu) === path\n}\n\n/**\n * 메뉴가 **같은 화면**을 가리키는가.\n *\n * 메뉴가 `a/b` 를 선언했다면 여기서는 안 맞는다(`a/b` ≠ `a`). 그 경우는 위의 자리 비교가 잡는다.\n */\nfunction pointsAtSamePage(menu: Menu, page: string) {\n return declaredPath(menu) === page\n}\n\n/**\n * 화면이 스스로 「나는 이 메뉴에 딸렸다」고 말하는가.\n *\n * 목록에서 줄을 눌러 들어가는 상세 화면은 메뉴에 아예 없다. 그러면 왼쪽 메뉴에 아무 표시가 없고,\n * 들어간 사람이 자기가 어디에 있는지 잃는다.\n *\n * 어느 목록에서 열리는지는 **그 화면이 가장 잘 안다** — 메뉴 쪽에 상세 화면 목록을 나열해 두면\n * 화면이 늘 때마다 그 목록을 같이 고쳐야 하고, 빠뜨린 화면만 조용히 표시를 잃는다.\n */\nfunction pageSaysItBelongsHere(menu: Menu, context?: { menu?: string }) {\n const belongsTo = context?.menu\n return !!belongsTo && declaredPath(menu) === belongsTo\n}\n\n/** 앱이 직접 판정을 주는 경우. 선언한 경로보다 뒤에 본다 — 적어 둔 경로가 더 분명한 뜻이다. */\nfunction saysItIsActive(menu: Menu, path: string, page: string) {\n return !!(menu.active && typeof menu.active === 'function' && menu.active.call(menu, { path, page } as any))\n}\n\n/**\n * 최상위와 그 안의 메뉴를 훑어 처음 맞는 것을 찾는다.\n *\n * ── 하위를 먼저 본다 ────────────────────────────────────────────────────────\n * **최상위의 주소는 대개 자기 하위 하나의 주소다.** 그 최상위를 눌렀을 때 갈 곳이 필요하고,\n * 그 자리를 하위 하나가 맡는다.\n *\n * ```\n * { name: 'my-work', path: 'todo-list', menus: [{ name: 'todo', path: 'todo-list' }, …] }\n * ```\n *\n * 최상위를 먼저 보면 주소 `todo-list` 에서 **최상위가 매치를 삼킨다.** 그러면 「할 일」이 표시를\n * 못 받고, 최상위마다 하위 하나가 그렇게 된다.\n *\n * 하위가 **더 구체적인 답**이다. 최상위는 묶음이고 그 주소는 하위로 가는 지름길이다.\n */\nfunction lookup(menus: Menu[], matches: (menu: Menu) => boolean): ActiveMenus | undefined {\n for (const top of menus) {\n const inner = top.menus?.find(matches)\n if (inner) {\n return { top, menu: inner }\n }\n\n if (matches(top)) {\n return { top, menu: top }\n }\n }\n\n return undefined\n}\n\n/**\n * 주소가 가리키는 메뉴.\n *\n * ── 여러 번 훑는 이유 — 순서가 뜻을 갖는다 ──────────────────────────────────\n * `a` 와 `a/b` 두 메뉴가 함께 있고 주소가 `a/b` 이면 **`a/b` 가 이겨야 한다.** 한 번에 훑으면서\n * 조건을 OR 로 묶으면 목록에서 먼저 나온 `a` 가 이기고, 실제로 보고 있는 자리를 가리키는 메뉴가\n * 표시를 못 받는다.\n *\n * 분명한 것부터 본다 — 바로 그 자리 · 화면이 말한 것 · 같은 화면 · 앱이 준 판정.\n */\nexport function menuForRoute(menus: Menu[], where: Where): ActiveMenus {\n const path = joinedPath(where)\n const page = where.page\n\n const found =\n lookup(menus, menu => pointsAtSamePlace(menu, path)) ||\n lookup(menus, menu => pageSaysItBelongsHere(menu, where.context)) ||\n lookup(menus, menu => pointsAtSamePage(menu, page) || saysItIsActive(menu, path, page))\n\n return { top: found?.top, menu: found?.menu || found?.top }\n}\n\n/**\n * 화면에 그릴 두 값을 정한다.\n *\n * ── 무엇이 무엇을 이기나 ────────────────────────────────────────────────────\n * ```\n * top 손으로 정한 것이 있으면 그것. 없으면 주소가 가리키는 것\n * menu 늘 주소에서 온다\n * ```\n *\n * `menu` 를 손으로 정한 것에 맡기지 않는 이유는, **다른 묶음을 열어 놓고 둘러보는 동안에도\n * 자기가 어느 화면에 있는지는 알아야** 하기 때문이다.\n */\nexport function resolveActive(menus: Menu[], where: Where, userOpened: UserOpened): ActiveMenus {\n const byRoute = menuForRoute(menus, where)\n\n return {\n top: userOpened === undefined ? byRoute.top : (userOpened ?? undefined),\n menu: byRoute.menu\n }\n}\n"]}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 왼쪽 메뉴가 지금 **어떤 모양으로 서 있나**, 그리고 그 모양에서 무엇이 달라지나.
|
|
3
|
+
*
|
|
4
|
+
* ## 왜 이 파일이 있나
|
|
5
|
+
*
|
|
6
|
+
* 이 규칙들이 컴포넌트 안에 흩어져 있었고, 한 자리를 고치면 다른 자리가 조용히 깨졌다.
|
|
7
|
+
* 실제로 났던 것들이다.
|
|
8
|
+
*
|
|
9
|
+
* ```
|
|
10
|
+
* 합계 배지 감추기 레일 스타일에 두었다 → 감추기 모드에서 그 기능만 빠졌다
|
|
11
|
+
* 시스템 자리 레일 가지에만 두었다 → 감추기 모드에서 한 줄이 사라졌다
|
|
12
|
+
* 팝업 · 노브 레일에만 걸었다 → 레일 밖에서 꾸밈 없이 떴다
|
|
13
|
+
* 서랍 판정 속성만 만들고 넘기지 않았다 → 아무 일도 안 일어났다
|
|
14
|
+
* ```
|
|
15
|
+
*
|
|
16
|
+
* 공통점은 **모양에 따라 달라지는 것과 달라지지 않는 것을 그때그때 판단한 것**이다. 그래서
|
|
17
|
+
* 물음을 두 개로 줄이고 답을 한 자리에 모은다. 여기 있는 것은 순수 함수라 시험으로 못박을 수
|
|
18
|
+
* 있다(§`test/menu-mode.test.ts`).
|
|
19
|
+
*
|
|
20
|
+
* ## 물음은 둘뿐이다
|
|
21
|
+
*
|
|
22
|
+
* ```
|
|
23
|
+
* 하위를 어디에 여나 제자리 · 팝업
|
|
24
|
+
* 최상위를 누르면 이동하나 한다 · 펼치기만
|
|
25
|
+
* ```
|
|
26
|
+
*
|
|
27
|
+
* 나머지(이름을 보이나, 배지를 어디에 두나)는 CSS 가 모양 하나로 답한다.
|
|
28
|
+
*/
|
|
29
|
+
/**
|
|
30
|
+
* 메뉴의 모양.
|
|
31
|
+
*
|
|
32
|
+
* ```
|
|
33
|
+
* list 늘 보이는 기존 메뉴. 폭이 고정이다
|
|
34
|
+
* rail 늘 보이고 접을 수 있다
|
|
35
|
+
* rail-collapsed 접힌 레일 — 아이콘만
|
|
36
|
+
* drawer 숨어 있다가 화면 위에 겹쳐 뜬다(감추기 설정 · 휴대폰)
|
|
37
|
+
* ```
|
|
38
|
+
*/
|
|
39
|
+
export type MenuMode = 'list' | 'rail' | 'rail-collapsed' | 'drawer';
|
|
40
|
+
/**
|
|
41
|
+
* 세 플래그에서 모양 하나를 정한다.
|
|
42
|
+
*
|
|
43
|
+
* **서랍이 가장 세다.** 숨어 있는 것을 접을 수는 없고, 화면 위에 뜬 것의 폭을 줄일 이유도 없다.
|
|
44
|
+
* 그래서 서랍이면 레일·접힘을 보지 않는다.
|
|
45
|
+
*/
|
|
46
|
+
export declare function menuMode(flags: {
|
|
47
|
+
rail?: boolean;
|
|
48
|
+
collapsed?: boolean;
|
|
49
|
+
drawer?: boolean;
|
|
50
|
+
}): MenuMode;
|
|
51
|
+
/**
|
|
52
|
+
* 하위 메뉴를 어디에 여나.
|
|
53
|
+
*
|
|
54
|
+
* **접힌 레일에서만 팝업이다.** 56px 안에 들여쓴 목록이 들어가지 않는다.
|
|
55
|
+
*
|
|
56
|
+
* 서랍에서는 팝업이 아니다 — 화면 위에 뜬 서랍 옆에 고정 팝업이 설 자리가 없고, 서랍은 대개
|
|
57
|
+
* 휴대폰에서 쓴다.
|
|
58
|
+
*/
|
|
59
|
+
export declare function childrenOpenAs(mode: MenuMode): 'inline' | 'popup';
|
|
60
|
+
/**
|
|
61
|
+
* 최상위를 누르면 그 대표 화면으로 이동하나.
|
|
62
|
+
*
|
|
63
|
+
* 최상위 항목은 자기 대표 화면의 주소를 갖는다(대개 하위 하나의 주소다). 늘 보이는 메뉴에서는
|
|
64
|
+
* 그것이 도움이 된다 — 누르면 어디든 도착한다.
|
|
65
|
+
*
|
|
66
|
+
* **서랍에서는 아니다.** 누르는 순간 서랍이 닫히므로 그 묶음의 나머지 하위를 볼 기회가 없다.
|
|
67
|
+
* 휴대폰에서 「작업」을 누르면 작업지시로 튀고 나머지 다섯은 못 본다.
|
|
68
|
+
*
|
|
69
|
+
* 접힌 레일에서도 아니다 — 그때는 팝업을 열어 고르게 한다.
|
|
70
|
+
*/
|
|
71
|
+
export declare function topNavigates(mode: MenuMode): boolean;
|
|
72
|
+
/**
|
|
73
|
+
* 최상위 항목의 이름이 보이나.
|
|
74
|
+
*
|
|
75
|
+
* 접힌 레일만 아이콘뿐이다. 이 답은 CSS 가 쓰지만, 표를 한자리에서 읽게 하려고 여기 둔다.
|
|
76
|
+
*/
|
|
77
|
+
export declare function showsLabels(mode: MenuMode): boolean;
|
|
78
|
+
/**
|
|
79
|
+
* 이 폭 아래로 좁아지면 스스로 접는다.
|
|
80
|
+
*
|
|
81
|
+
* 208px 에서 이름이 다 들어간다. 140px 밑으로 가면 「기준정보」가 「기준…」이 되고 이름이 이름
|
|
82
|
+
* 노릇을 못 한다. **그 상태로 두는 것보다 아이콘 줄이 낫다** — 아이콘은 좁아도 그대로 읽히고,
|
|
83
|
+
* 하위는 팝업으로 열린다.
|
|
84
|
+
*/
|
|
85
|
+
export declare const AUTO_COLLAPSE_UNDER = 140;
|
|
86
|
+
/**
|
|
87
|
+
* 지금 폭에서 스스로 접어야 하나.
|
|
88
|
+
*
|
|
89
|
+
* ── 되풀이를 막는 두 조건 ───────────────────────────────────────────────────
|
|
90
|
+
* 접으면 폭이 56px 이 되어 이 판정에 다시 걸린다. 그래서 **이미 접혔으면 보지 않는다.**
|
|
91
|
+
*
|
|
92
|
+
* 그리고 폭이 0 일 때는 판단하지 않는다 — 처음 붙는 순간이나 감춰진 순간이라, 그때 접으면
|
|
93
|
+
* 사용자가 고른 적 없는 상태가 남는다.
|
|
94
|
+
*/
|
|
95
|
+
export declare function shouldAutoCollapse(width: number, flags: {
|
|
96
|
+
rail?: boolean;
|
|
97
|
+
collapsed?: boolean;
|
|
98
|
+
}): boolean;
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 왼쪽 메뉴가 지금 **어떤 모양으로 서 있나**, 그리고 그 모양에서 무엇이 달라지나.
|
|
3
|
+
*
|
|
4
|
+
* ## 왜 이 파일이 있나
|
|
5
|
+
*
|
|
6
|
+
* 이 규칙들이 컴포넌트 안에 흩어져 있었고, 한 자리를 고치면 다른 자리가 조용히 깨졌다.
|
|
7
|
+
* 실제로 났던 것들이다.
|
|
8
|
+
*
|
|
9
|
+
* ```
|
|
10
|
+
* 합계 배지 감추기 레일 스타일에 두었다 → 감추기 모드에서 그 기능만 빠졌다
|
|
11
|
+
* 시스템 자리 레일 가지에만 두었다 → 감추기 모드에서 한 줄이 사라졌다
|
|
12
|
+
* 팝업 · 노브 레일에만 걸었다 → 레일 밖에서 꾸밈 없이 떴다
|
|
13
|
+
* 서랍 판정 속성만 만들고 넘기지 않았다 → 아무 일도 안 일어났다
|
|
14
|
+
* ```
|
|
15
|
+
*
|
|
16
|
+
* 공통점은 **모양에 따라 달라지는 것과 달라지지 않는 것을 그때그때 판단한 것**이다. 그래서
|
|
17
|
+
* 물음을 두 개로 줄이고 답을 한 자리에 모은다. 여기 있는 것은 순수 함수라 시험으로 못박을 수
|
|
18
|
+
* 있다(§`test/menu-mode.test.ts`).
|
|
19
|
+
*
|
|
20
|
+
* ## 물음은 둘뿐이다
|
|
21
|
+
*
|
|
22
|
+
* ```
|
|
23
|
+
* 하위를 어디에 여나 제자리 · 팝업
|
|
24
|
+
* 최상위를 누르면 이동하나 한다 · 펼치기만
|
|
25
|
+
* ```
|
|
26
|
+
*
|
|
27
|
+
* 나머지(이름을 보이나, 배지를 어디에 두나)는 CSS 가 모양 하나로 답한다.
|
|
28
|
+
*/
|
|
29
|
+
/**
|
|
30
|
+
* 세 플래그에서 모양 하나를 정한다.
|
|
31
|
+
*
|
|
32
|
+
* **서랍이 가장 세다.** 숨어 있는 것을 접을 수는 없고, 화면 위에 뜬 것의 폭을 줄일 이유도 없다.
|
|
33
|
+
* 그래서 서랍이면 레일·접힘을 보지 않는다.
|
|
34
|
+
*/
|
|
35
|
+
export function menuMode(flags) {
|
|
36
|
+
if (flags.drawer) {
|
|
37
|
+
return 'drawer';
|
|
38
|
+
}
|
|
39
|
+
if (!flags.rail) {
|
|
40
|
+
return 'list';
|
|
41
|
+
}
|
|
42
|
+
return flags.collapsed ? 'rail-collapsed' : 'rail';
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* 하위 메뉴를 어디에 여나.
|
|
46
|
+
*
|
|
47
|
+
* **접힌 레일에서만 팝업이다.** 56px 안에 들여쓴 목록이 들어가지 않는다.
|
|
48
|
+
*
|
|
49
|
+
* 서랍에서는 팝업이 아니다 — 화면 위에 뜬 서랍 옆에 고정 팝업이 설 자리가 없고, 서랍은 대개
|
|
50
|
+
* 휴대폰에서 쓴다.
|
|
51
|
+
*/
|
|
52
|
+
export function childrenOpenAs(mode) {
|
|
53
|
+
return mode === 'rail-collapsed' ? 'popup' : 'inline';
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* 최상위를 누르면 그 대표 화면으로 이동하나.
|
|
57
|
+
*
|
|
58
|
+
* 최상위 항목은 자기 대표 화면의 주소를 갖는다(대개 하위 하나의 주소다). 늘 보이는 메뉴에서는
|
|
59
|
+
* 그것이 도움이 된다 — 누르면 어디든 도착한다.
|
|
60
|
+
*
|
|
61
|
+
* **서랍에서는 아니다.** 누르는 순간 서랍이 닫히므로 그 묶음의 나머지 하위를 볼 기회가 없다.
|
|
62
|
+
* 휴대폰에서 「작업」을 누르면 작업지시로 튀고 나머지 다섯은 못 본다.
|
|
63
|
+
*
|
|
64
|
+
* 접힌 레일에서도 아니다 — 그때는 팝업을 열어 고르게 한다.
|
|
65
|
+
*/
|
|
66
|
+
export function topNavigates(mode) {
|
|
67
|
+
return mode === 'list' || mode === 'rail';
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* 최상위 항목의 이름이 보이나.
|
|
71
|
+
*
|
|
72
|
+
* 접힌 레일만 아이콘뿐이다. 이 답은 CSS 가 쓰지만, 표를 한자리에서 읽게 하려고 여기 둔다.
|
|
73
|
+
*/
|
|
74
|
+
export function showsLabels(mode) {
|
|
75
|
+
return mode !== 'rail-collapsed';
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* 이 폭 아래로 좁아지면 스스로 접는다.
|
|
79
|
+
*
|
|
80
|
+
* 208px 에서 이름이 다 들어간다. 140px 밑으로 가면 「기준정보」가 「기준…」이 되고 이름이 이름
|
|
81
|
+
* 노릇을 못 한다. **그 상태로 두는 것보다 아이콘 줄이 낫다** — 아이콘은 좁아도 그대로 읽히고,
|
|
82
|
+
* 하위는 팝업으로 열린다.
|
|
83
|
+
*/
|
|
84
|
+
export const AUTO_COLLAPSE_UNDER = 140;
|
|
85
|
+
/**
|
|
86
|
+
* 지금 폭에서 스스로 접어야 하나.
|
|
87
|
+
*
|
|
88
|
+
* ── 되풀이를 막는 두 조건 ───────────────────────────────────────────────────
|
|
89
|
+
* 접으면 폭이 56px 이 되어 이 판정에 다시 걸린다. 그래서 **이미 접혔으면 보지 않는다.**
|
|
90
|
+
*
|
|
91
|
+
* 그리고 폭이 0 일 때는 판단하지 않는다 — 처음 붙는 순간이나 감춰진 순간이라, 그때 접으면
|
|
92
|
+
* 사용자가 고른 적 없는 상태가 남는다.
|
|
93
|
+
*/
|
|
94
|
+
export function shouldAutoCollapse(width, flags) {
|
|
95
|
+
if (!flags.rail || flags.collapsed) {
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
return width > 0 && width <= AUTO_COLLAPSE_UNDER;
|
|
99
|
+
}
|
|
100
|
+
//# sourceMappingURL=menu-mode.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"menu-mode.js","sourceRoot":"","sources":["../../src/menu-mode.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAcH;;;;;GAKG;AACH,MAAM,UAAU,QAAQ,CAAC,KAAgE;IACvF,IAAI,KAAK,CAAC,MAAM,EAAE,CAAC;QACjB,OAAO,QAAQ,CAAA;IACjB,CAAC;IAED,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC;QAChB,OAAO,MAAM,CAAA;IACf,CAAC;IAED,OAAO,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC,gBAAgB,CAAC,CAAC,CAAC,MAAM,CAAA;AACpD,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,cAAc,CAAC,IAAc;IAC3C,OAAO,IAAI,KAAK,gBAAgB,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,QAAQ,CAAA;AACvD,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,YAAY,CAAC,IAAc;IACzC,OAAO,IAAI,KAAK,MAAM,IAAI,IAAI,KAAK,MAAM,CAAA;AAC3C,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,WAAW,CAAC,IAAc;IACxC,OAAO,IAAI,KAAK,gBAAgB,CAAA;AAClC,CAAC;AAED;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG,GAAG,CAAA;AAEtC;;;;;;;;GAQG;AACH,MAAM,UAAU,kBAAkB,CAAC,KAAa,EAAE,KAA8C;IAC9F,IAAI,CAAC,KAAK,CAAC,IAAI,IAAI,KAAK,CAAC,SAAS,EAAE,CAAC;QACnC,OAAO,KAAK,CAAA;IACd,CAAC;IAED,OAAO,KAAK,GAAG,CAAC,IAAI,KAAK,IAAI,mBAAmB,CAAA;AAClD,CAAC","sourcesContent":["/**\n * 왼쪽 메뉴가 지금 **어떤 모양으로 서 있나**, 그리고 그 모양에서 무엇이 달라지나.\n *\n * ## 왜 이 파일이 있나\n *\n * 이 규칙들이 컴포넌트 안에 흩어져 있었고, 한 자리를 고치면 다른 자리가 조용히 깨졌다.\n * 실제로 났던 것들이다.\n *\n * ```\n * 합계 배지 감추기 레일 스타일에 두었다 → 감추기 모드에서 그 기능만 빠졌다\n * 시스템 자리 레일 가지에만 두었다 → 감추기 모드에서 한 줄이 사라졌다\n * 팝업 · 노브 레일에만 걸었다 → 레일 밖에서 꾸밈 없이 떴다\n * 서랍 판정 속성만 만들고 넘기지 않았다 → 아무 일도 안 일어났다\n * ```\n *\n * 공통점은 **모양에 따라 달라지는 것과 달라지지 않는 것을 그때그때 판단한 것**이다. 그래서\n * 물음을 두 개로 줄이고 답을 한 자리에 모은다. 여기 있는 것은 순수 함수라 시험으로 못박을 수\n * 있다(§`test/menu-mode.test.ts`).\n *\n * ## 물음은 둘뿐이다\n *\n * ```\n * 하위를 어디에 여나 제자리 · 팝업\n * 최상위를 누르면 이동하나 한다 · 펼치기만\n * ```\n *\n * 나머지(이름을 보이나, 배지를 어디에 두나)는 CSS 가 모양 하나로 답한다.\n */\n\n/**\n * 메뉴의 모양.\n *\n * ```\n * list 늘 보이는 기존 메뉴. 폭이 고정이다\n * rail 늘 보이고 접을 수 있다\n * rail-collapsed 접힌 레일 — 아이콘만\n * drawer 숨어 있다가 화면 위에 겹쳐 뜬다(감추기 설정 · 휴대폰)\n * ```\n */\nexport type MenuMode = 'list' | 'rail' | 'rail-collapsed' | 'drawer'\n\n/**\n * 세 플래그에서 모양 하나를 정한다.\n *\n * **서랍이 가장 세다.** 숨어 있는 것을 접을 수는 없고, 화면 위에 뜬 것의 폭을 줄일 이유도 없다.\n * 그래서 서랍이면 레일·접힘을 보지 않는다.\n */\nexport function menuMode(flags: { rail?: boolean; collapsed?: boolean; drawer?: boolean }): MenuMode {\n if (flags.drawer) {\n return 'drawer'\n }\n\n if (!flags.rail) {\n return 'list'\n }\n\n return flags.collapsed ? 'rail-collapsed' : 'rail'\n}\n\n/**\n * 하위 메뉴를 어디에 여나.\n *\n * **접힌 레일에서만 팝업이다.** 56px 안에 들여쓴 목록이 들어가지 않는다.\n *\n * 서랍에서는 팝업이 아니다 — 화면 위에 뜬 서랍 옆에 고정 팝업이 설 자리가 없고, 서랍은 대개\n * 휴대폰에서 쓴다.\n */\nexport function childrenOpenAs(mode: MenuMode): 'inline' | 'popup' {\n return mode === 'rail-collapsed' ? 'popup' : 'inline'\n}\n\n/**\n * 최상위를 누르면 그 대표 화면으로 이동하나.\n *\n * 최상위 항목은 자기 대표 화면의 주소를 갖는다(대개 하위 하나의 주소다). 늘 보이는 메뉴에서는\n * 그것이 도움이 된다 — 누르면 어디든 도착한다.\n *\n * **서랍에서는 아니다.** 누르는 순간 서랍이 닫히므로 그 묶음의 나머지 하위를 볼 기회가 없다.\n * 휴대폰에서 「작업」을 누르면 작업지시로 튀고 나머지 다섯은 못 본다.\n *\n * 접힌 레일에서도 아니다 — 그때는 팝업을 열어 고르게 한다.\n */\nexport function topNavigates(mode: MenuMode): boolean {\n return mode === 'list' || mode === 'rail'\n}\n\n/**\n * 최상위 항목의 이름이 보이나.\n *\n * 접힌 레일만 아이콘뿐이다. 이 답은 CSS 가 쓰지만, 표를 한자리에서 읽게 하려고 여기 둔다.\n */\nexport function showsLabels(mode: MenuMode): boolean {\n return mode !== 'rail-collapsed'\n}\n\n/**\n * 이 폭 아래로 좁아지면 스스로 접는다.\n *\n * 208px 에서 이름이 다 들어간다. 140px 밑으로 가면 「기준정보」가 「기준…」이 되고 이름이 이름\n * 노릇을 못 한다. **그 상태로 두는 것보다 아이콘 줄이 낫다** — 아이콘은 좁아도 그대로 읽히고,\n * 하위는 팝업으로 열린다.\n */\nexport const AUTO_COLLAPSE_UNDER = 140\n\n/**\n * 지금 폭에서 스스로 접어야 하나.\n *\n * ── 되풀이를 막는 두 조건 ───────────────────────────────────────────────────\n * 접으면 폭이 56px 이 되어 이 판정에 다시 걸린다. 그래서 **이미 접혔으면 보지 않는다.**\n *\n * 그리고 폭이 0 일 때는 판단하지 않는다 — 처음 붙는 순간이나 감춰진 순간이라, 그때 접으면\n * 사용자가 고른 적 없는 상태가 남는다.\n */\nexport function shouldAutoCollapse(width: number, flags: { rail?: boolean; collapsed?: boolean }): boolean {\n if (!flags.rail || flags.collapsed) {\n return false\n }\n\n return width > 0 && width <= AUTO_COLLAPSE_UNDER\n}\n"]}
|
|
@@ -1 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 왼쪽 메뉴의 모양 — **한 벌이다.**
|
|
3
|
+
*
|
|
4
|
+
* ## 왜 한 벌인가
|
|
5
|
+
*
|
|
6
|
+
* 한때 둘이었다. 기존 모양(줄마다 밑줄 · 왼쪽 여백 16px · hover 에 굵기 변화)과 레일 모양
|
|
7
|
+
* (라운드 알약 · 밑줄 없음 · 접을 수 있음). 같은 메뉴가 설정 하나로 딴 물건처럼 보였고, 무엇을
|
|
8
|
+
* 어느 쪽에 두어야 하는지를 매번 판단해야 해서 **일곱 번 조용히 깨졌다.**
|
|
9
|
+
*
|
|
10
|
+
* 기존 모양은 접을 수도 없었다 — 줄마다 폭 전체를 쓰는 밑줄이 있고 왼쪽 여백이 16px 이라
|
|
11
|
+
* 56px 에서 무너진다. 그래서 레일 펼친 모양을 기준으로 통일했다.
|
|
12
|
+
*
|
|
13
|
+
* ## 안에 세 변형이 있다
|
|
14
|
+
*
|
|
15
|
+
* ```
|
|
16
|
+
* (기본) 늘 보이는 모양. 서랍도 이것이다
|
|
17
|
+
* [collapsed] 접힌 레일 — 아이콘만. 하위는 팝업으로
|
|
18
|
+
* .railpop 팝업 — 접힌 레일에서 하위로 가는 길, 그리고 시스템 항목의 자리
|
|
19
|
+
* ```
|
|
20
|
+
*
|
|
21
|
+
* 무엇이 어느 변형에 걸리나는 `menu-mode.ts` 가 답한다. 여기서 그때그때 판단하지 않는다.
|
|
22
|
+
*
|
|
23
|
+
* ## 값의 출처
|
|
24
|
+
*
|
|
25
|
+
* `operato-twin` 의 레일(`twin-nav-rail`)이 쓰던 값이다. 값을 베끼지 않고 조정 지점으로 노출해
|
|
26
|
+
* 앱이 자기 테마에 맞출 수 있게 둔다.
|
|
27
|
+
*/
|
|
1
28
|
export declare const MenuPortraitStyles: import("lit").CSSResult;
|