@operato/menu 10.29.30 → 10.29.35
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 +17 -0
- package/coverage/lcov-report/base.css +224 -0
- package/coverage/lcov-report/block-navigation.js +87 -0
- package/coverage/lcov-report/favicon.png +0 -0
- package/coverage/lcov-report/index.html +1076 -0
- package/coverage/lcov-report/prettify.css +1 -0
- package/coverage/lcov-report/prettify.js +2 -0
- package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
- package/coverage/lcov-report/sorter.js +210 -0
- package/coverage/lcov.info +60434 -0
- package/dist/src/menu-active.d.ts +6 -1
- package/dist/src/menu-active.js.map +1 -1
- package/dist/src/menu-mode.d.ts +34 -0
- package/dist/src/menu-mode.js +25 -0
- package/dist/src/menu-mode.js.map +1 -1
- package/dist/src/ox-menu-part.d.ts +28 -3
- package/dist/src/ox-menu-part.js +67 -11
- package/dist/src/ox-menu-part.js.map +1 -1
- package/dist/test/menu-rail-page.test.d.ts +1 -0
- package/dist/test/menu-rail-page.test.js +99 -0
- package/dist/test/menu-rail-page.test.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +3 -3
- package/test/menu-rail-page.test.ts +123 -0
|
@@ -33,9 +33,14 @@ export type Where = {
|
|
|
33
33
|
page: string;
|
|
34
34
|
/** 그 화면 안의 자리. 없을 수 있다. */
|
|
35
35
|
resourceId?: string;
|
|
36
|
-
/**
|
|
36
|
+
/**
|
|
37
|
+
* 화면이 스스로 「나는 이 메뉴에 딸렸다」고 말한 것.
|
|
38
|
+
*
|
|
39
|
+
* `rail` is the page's word on the rail while it is open (see `railCollapsed` in `menu-mode.ts`).
|
|
40
|
+
*/
|
|
37
41
|
context?: {
|
|
38
42
|
menu?: string;
|
|
43
|
+
rail?: string;
|
|
39
44
|
};
|
|
40
45
|
};
|
|
41
46
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"menu-active.js","sourceRoot":"","sources":["../../src/menu-active.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"menu-active.js","sourceRoot":"","sources":["../../src/menu-active.ts"],"names":[],"mappings":"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","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 * 화면이 스스로 「나는 이 메뉴에 딸렸다」고 말한 것.\n *\n * `rail` is the page's word on the rail while it is open (see `railCollapsed` in `menu-mode.ts`).\n */\n context?: { menu?: string; rail?: 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"]}
|
package/dist/src/menu-mode.d.ts
CHANGED
|
@@ -96,3 +96,37 @@ export declare function shouldAutoCollapse(width: number, flags: {
|
|
|
96
96
|
rail?: boolean;
|
|
97
97
|
collapsed?: boolean;
|
|
98
98
|
}): boolean;
|
|
99
|
+
/**
|
|
100
|
+
* What a page may say about the rail while it is open.
|
|
101
|
+
*
|
|
102
|
+
* ```
|
|
103
|
+
* collapsed fold the rail while this page is open
|
|
104
|
+
* ```
|
|
105
|
+
*
|
|
106
|
+
* A canvas page (the Figure modeller) needs the width; a list page does not. The page knows which it is, so it says
|
|
107
|
+
* it in its `context` the same way it says which menu it belongs to (`context.menu`).
|
|
108
|
+
*/
|
|
109
|
+
export type PageRail = 'collapsed';
|
|
110
|
+
/** Does the page ask for the rail to fold? Anything other than the one declared value is no request. */
|
|
111
|
+
export declare function pageAsksRailCollapsed(context?: {
|
|
112
|
+
rail?: string;
|
|
113
|
+
} | null): boolean;
|
|
114
|
+
/**
|
|
115
|
+
* Is the rail collapsed right now.
|
|
116
|
+
*
|
|
117
|
+
* ```
|
|
118
|
+
* stored what the person chose (kept in the browser)
|
|
119
|
+
* pageAsks the open page asked for the rail to fold
|
|
120
|
+
* onPage what the person chose on that page, if they toggled there
|
|
121
|
+
* ```
|
|
122
|
+
*
|
|
123
|
+
* ── The person's own choice is never overwritten ─────────────────────────────
|
|
124
|
+
* A page that asks only folds the rail while it is open. Leaving it brings back the stored state, and a toggle on
|
|
125
|
+
* that page lasts while the page is open and is not stored. Otherwise opening the modeller once would fold the rail
|
|
126
|
+
* in every other app screen from then on.
|
|
127
|
+
*/
|
|
128
|
+
export declare function railCollapsed(state: {
|
|
129
|
+
stored: boolean;
|
|
130
|
+
pageAsks: boolean;
|
|
131
|
+
onPage?: boolean;
|
|
132
|
+
}): boolean;
|
package/dist/src/menu-mode.js
CHANGED
|
@@ -97,4 +97,29 @@ export function shouldAutoCollapse(width, flags) {
|
|
|
97
97
|
}
|
|
98
98
|
return width > 0 && width <= AUTO_COLLAPSE_UNDER;
|
|
99
99
|
}
|
|
100
|
+
/** Does the page ask for the rail to fold? Anything other than the one declared value is no request. */
|
|
101
|
+
export function pageAsksRailCollapsed(context) {
|
|
102
|
+
return (context === null || context === void 0 ? void 0 : context.rail) === 'collapsed';
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Is the rail collapsed right now.
|
|
106
|
+
*
|
|
107
|
+
* ```
|
|
108
|
+
* stored what the person chose (kept in the browser)
|
|
109
|
+
* pageAsks the open page asked for the rail to fold
|
|
110
|
+
* onPage what the person chose on that page, if they toggled there
|
|
111
|
+
* ```
|
|
112
|
+
*
|
|
113
|
+
* ── The person's own choice is never overwritten ─────────────────────────────
|
|
114
|
+
* A page that asks only folds the rail while it is open. Leaving it brings back the stored state, and a toggle on
|
|
115
|
+
* that page lasts while the page is open and is not stored. Otherwise opening the modeller once would fold the rail
|
|
116
|
+
* in every other app screen from then on.
|
|
117
|
+
*/
|
|
118
|
+
export function railCollapsed(state) {
|
|
119
|
+
var _a;
|
|
120
|
+
if (!state.pageAsks) {
|
|
121
|
+
return state.stored;
|
|
122
|
+
}
|
|
123
|
+
return (_a = state.onPage) !== null && _a !== void 0 ? _a : true;
|
|
124
|
+
}
|
|
100
125
|
//# sourceMappingURL=menu-mode.js.map
|
|
@@ -1 +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
|
+
{"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;AAcD,wGAAwG;AACxG,MAAM,UAAU,qBAAqB,CAAC,OAAkC;IACtE,OAAO,CAAA,OAAO,aAAP,OAAO,uBAAP,OAAO,CAAE,IAAI,MAAK,WAAW,CAAA;AACtC,CAAC;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,aAAa,CAAC,KAA+D;;IAC3F,IAAI,CAAC,KAAK,CAAC,QAAQ,EAAE,CAAC;QACpB,OAAO,KAAK,CAAC,MAAM,CAAA;IACrB,CAAC;IAED,OAAO,MAAA,KAAK,CAAC,MAAM,mCAAI,IAAI,CAAA;AAC7B,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\n/**\n * What a page may say about the rail while it is open.\n *\n * ```\n * collapsed fold the rail while this page is open\n * ```\n *\n * A canvas page (the Figure modeller) needs the width; a list page does not. The page knows which it is, so it says\n * it in its `context` the same way it says which menu it belongs to (`context.menu`).\n */\nexport type PageRail = 'collapsed'\n\n/** Does the page ask for the rail to fold? Anything other than the one declared value is no request. */\nexport function pageAsksRailCollapsed(context?: { rail?: string } | null): boolean {\n return context?.rail === 'collapsed'\n}\n\n/**\n * Is the rail collapsed right now.\n *\n * ```\n * stored what the person chose (kept in the browser)\n * pageAsks the open page asked for the rail to fold\n * onPage what the person chose on that page, if they toggled there\n * ```\n *\n * ── The person's own choice is never overwritten ─────────────────────────────\n * A page that asks only folds the rail while it is open. Leaving it brings back the stored state, and a toggle on\n * that page lasts while the page is open and is not stored. Otherwise opening the modeller once would fold the rail\n * in every other app screen from then on.\n */\nexport function railCollapsed(state: { stored: boolean; pageAsks: boolean; onPage?: boolean }): boolean {\n if (!state.pageAsks) {\n return state.stored\n }\n\n return state.onPage ?? true\n}\n"]}
|
|
@@ -19,8 +19,21 @@ export declare class OxMenuPart extends LitElement {
|
|
|
19
19
|
* 가로 메뉴(`landscape`)에는 뜻이 없다 — 접을 축이 다르다. 그때는 무시한다.
|
|
20
20
|
*/
|
|
21
21
|
rail: boolean;
|
|
22
|
-
/**
|
|
22
|
+
/**
|
|
23
|
+
* 접힘 — 아이콘만. 사용자가 고른 것은 브라우저에 남긴다.
|
|
24
|
+
*
|
|
25
|
+
* This is the state the rail stands in now. What the person chose is `_storedCollapsed`; a page that asks for the
|
|
26
|
+
* rail to fold changes this value only while it is open (`railCollapsed`).
|
|
27
|
+
*/
|
|
23
28
|
collapsed: boolean;
|
|
29
|
+
/** What the person chose, kept in the browser. A page's request never writes here. */
|
|
30
|
+
private _storedCollapsed;
|
|
31
|
+
/** The open page asked for the rail to fold (`context.rail`). */
|
|
32
|
+
private _pageAsks;
|
|
33
|
+
/** What the person chose on the page that asked, while it is open. Forgotten when the page changes. */
|
|
34
|
+
private _onPage?;
|
|
35
|
+
/** The page the two values above were read on. */
|
|
36
|
+
private _railPage?;
|
|
24
37
|
/**
|
|
25
38
|
* 이 메뉴가 **서랍**인가 — 화면 위에 겹쳐 떴다가 닫히는 상태.
|
|
26
39
|
*
|
|
@@ -71,7 +84,7 @@ export declare class OxMenuPart extends LitElement {
|
|
|
71
84
|
* 치우고 있으므로, 그 짝을 같은 자리에 둔다.
|
|
72
85
|
*/
|
|
73
86
|
private syncSplitter;
|
|
74
|
-
protected updated(): void;
|
|
87
|
+
protected updated(changed: Map<PropertyKey, unknown>): void;
|
|
75
88
|
/**
|
|
76
89
|
* 접기/펼치기.
|
|
77
90
|
*
|
|
@@ -79,7 +92,12 @@ export declare class OxMenuPart extends LitElement {
|
|
|
79
92
|
* `twin-nav-rail` 이 같은 자리에서 같은 일을 한다.
|
|
80
93
|
*/
|
|
81
94
|
private toggleCollapsed;
|
|
82
|
-
/**
|
|
95
|
+
/**
|
|
96
|
+
* 접힘을 정하고 남긴다. 스스로 접을 때와 손으로 접을 때가 이 뒷부분을 함께 쓴다.
|
|
97
|
+
*
|
|
98
|
+
* On a page that asked for the rail to fold, the choice lasts while that page is open and is not stored -- leaving
|
|
99
|
+
* brings back what the person chose before. The resize event goes out from `updated`.
|
|
100
|
+
*/
|
|
83
101
|
private setCollapsed;
|
|
84
102
|
_activeTopLevel?: Menu;
|
|
85
103
|
_activeMenu?: Menu;
|
|
@@ -118,4 +136,11 @@ export declare class OxMenuPart extends LitElement {
|
|
|
118
136
|
* 것을 넘기고 결과를 상태에 넣는 일만 한다.
|
|
119
137
|
*/
|
|
120
138
|
private findActivePage;
|
|
139
|
+
/**
|
|
140
|
+
* Fold the rail while the open page asks for it, and bring back the person's choice when it no longer does.
|
|
141
|
+
*
|
|
142
|
+
* The rule is `railCollapsed` in `menu-mode.ts`. A toggle made on the asking page is forgotten when the page
|
|
143
|
+
* changes, so the next visit folds again.
|
|
144
|
+
*/
|
|
145
|
+
private applyPageRail;
|
|
121
146
|
}
|
package/dist/src/ox-menu-part.js
CHANGED
|
@@ -8,7 +8,7 @@ import { customElement, property, state } from 'lit/decorators.js';
|
|
|
8
8
|
import { RouteController, MenuController } from '@operato/shell';
|
|
9
9
|
import { ScrollbarStyles } from '@operato/styles';
|
|
10
10
|
import { resolveActive, joinedPath } from './menu-active.js';
|
|
11
|
-
import { shouldAutoCollapse } from './menu-mode.js';
|
|
11
|
+
import { pageAsksRailCollapsed, railCollapsed, shouldAutoCollapse } from './menu-mode.js';
|
|
12
12
|
let OxMenuPart = OxMenuPart_1 = class OxMenuPart extends LitElement {
|
|
13
13
|
constructor() {
|
|
14
14
|
super(...arguments);
|
|
@@ -25,8 +25,17 @@ let OxMenuPart = OxMenuPart_1 = class OxMenuPart extends LitElement {
|
|
|
25
25
|
* 가로 메뉴(`landscape`)에는 뜻이 없다 — 접을 축이 다르다. 그때는 무시한다.
|
|
26
26
|
*/
|
|
27
27
|
this.rail = false;
|
|
28
|
-
/**
|
|
28
|
+
/**
|
|
29
|
+
* 접힘 — 아이콘만. 사용자가 고른 것은 브라우저에 남긴다.
|
|
30
|
+
*
|
|
31
|
+
* This is the state the rail stands in now. What the person chose is `_storedCollapsed`; a page that asks for the
|
|
32
|
+
* rail to fold changes this value only while it is open (`railCollapsed`).
|
|
33
|
+
*/
|
|
29
34
|
this.collapsed = false;
|
|
35
|
+
/** What the person chose, kept in the browser. A page's request never writes here. */
|
|
36
|
+
this._storedCollapsed = false;
|
|
37
|
+
/** The open page asked for the rail to fold (`context.rail`). */
|
|
38
|
+
this._pageAsks = false;
|
|
30
39
|
/**
|
|
31
40
|
* 이 메뉴가 **서랍**인가 — 화면 위에 겹쳐 떴다가 닫히는 상태.
|
|
32
41
|
*
|
|
@@ -55,7 +64,8 @@ let OxMenuPart = OxMenuPart_1 = class OxMenuPart extends LitElement {
|
|
|
55
64
|
connectedCallback() {
|
|
56
65
|
super.connectedCallback();
|
|
57
66
|
try {
|
|
58
|
-
this.
|
|
67
|
+
this._storedCollapsed = localStorage.getItem(OxMenuPart_1.LS_KEY) === '1';
|
|
68
|
+
this.collapsed = this._storedCollapsed;
|
|
59
69
|
}
|
|
60
70
|
catch (_a) {
|
|
61
71
|
/* 막힐 수 있다(사생활 보호 모드). 펼쳐진 상태가 안전한 기본이다. */
|
|
@@ -135,8 +145,15 @@ let OxMenuPart = OxMenuPart_1 = class OxMenuPart extends LitElement {
|
|
|
135
145
|
splitter.hidden = this.collapsed;
|
|
136
146
|
}
|
|
137
147
|
}
|
|
138
|
-
updated() {
|
|
148
|
+
updated(changed) {
|
|
139
149
|
this.syncSplitter();
|
|
150
|
+
/*
|
|
151
|
+
* A page's request changes the width without a click, so the canvas has to measure again the same way it does
|
|
152
|
+
* after a toggle. The first render is not a change -- `changed` then holds the initial `undefined`.
|
|
153
|
+
*/
|
|
154
|
+
if (changed.has('collapsed') && changed.get('collapsed') !== undefined) {
|
|
155
|
+
window.dispatchEvent(new Event('resize'));
|
|
156
|
+
}
|
|
140
157
|
}
|
|
141
158
|
/**
|
|
142
159
|
* 접기/펼치기.
|
|
@@ -158,16 +175,26 @@ let OxMenuPart = OxMenuPart_1 = class OxMenuPart extends LitElement {
|
|
|
158
175
|
}
|
|
159
176
|
this.setCollapsed(next);
|
|
160
177
|
}
|
|
161
|
-
/**
|
|
178
|
+
/**
|
|
179
|
+
* 접힘을 정하고 남긴다. 스스로 접을 때와 손으로 접을 때가 이 뒷부분을 함께 쓴다.
|
|
180
|
+
*
|
|
181
|
+
* On a page that asked for the rail to fold, the choice lasts while that page is open and is not stored -- leaving
|
|
182
|
+
* brings back what the person chose before. The resize event goes out from `updated`.
|
|
183
|
+
*/
|
|
162
184
|
setCollapsed(next) {
|
|
163
|
-
this.
|
|
164
|
-
|
|
165
|
-
localStorage.setItem(OxMenuPart_1.LS_KEY, next ? '1' : '0');
|
|
185
|
+
if (this._pageAsks) {
|
|
186
|
+
this._onPage = next;
|
|
166
187
|
}
|
|
167
|
-
|
|
168
|
-
|
|
188
|
+
else {
|
|
189
|
+
this._storedCollapsed = next;
|
|
190
|
+
try {
|
|
191
|
+
localStorage.setItem(OxMenuPart_1.LS_KEY, next ? '1' : '0');
|
|
192
|
+
}
|
|
193
|
+
catch (_a) {
|
|
194
|
+
/* 못 남기면 다음에 펼쳐진 채로 뜬다. 기능은 그대로다. */
|
|
195
|
+
}
|
|
169
196
|
}
|
|
170
|
-
|
|
197
|
+
this.collapsed = next;
|
|
171
198
|
}
|
|
172
199
|
render() {
|
|
173
200
|
const menus = this._menu.menus;
|
|
@@ -244,6 +271,35 @@ let OxMenuPart = OxMenuPart_1 = class OxMenuPart extends LitElement {
|
|
|
244
271
|
this._path = path;
|
|
245
272
|
this._activeTopLevel = top;
|
|
246
273
|
this._activeMenu = menu;
|
|
274
|
+
this.applyPageRail(page, context);
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
* Fold the rail while the open page asks for it, and bring back the person's choice when it no longer does.
|
|
278
|
+
*
|
|
279
|
+
* The rule is `railCollapsed` in `menu-mode.ts`. A toggle made on the asking page is forgotten when the page
|
|
280
|
+
* changes, so the next visit folds again.
|
|
281
|
+
*/
|
|
282
|
+
applyPageRail(page, context) {
|
|
283
|
+
const pageAsks = this.rail && pageAsksRailCollapsed(context);
|
|
284
|
+
if (page !== this._railPage || pageAsks !== this._pageAsks) {
|
|
285
|
+
this._onPage = undefined;
|
|
286
|
+
}
|
|
287
|
+
this._railPage = page;
|
|
288
|
+
this._pageAsks = pageAsks;
|
|
289
|
+
const next = railCollapsed({ stored: this._storedCollapsed, pageAsks, onPage: this._onPage });
|
|
290
|
+
if (next === this.collapsed) {
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
/* Folding clears a width dragged with the handle, the same as a toggle; unfolding gives it back. */
|
|
294
|
+
if (next && this.style.width) {
|
|
295
|
+
this._draggedWidth = this.style.width;
|
|
296
|
+
this.style.width = '';
|
|
297
|
+
}
|
|
298
|
+
else if (!next && this._draggedWidth) {
|
|
299
|
+
this.style.width = this._draggedWidth;
|
|
300
|
+
this._draggedWidth = null;
|
|
301
|
+
}
|
|
302
|
+
this.collapsed = next;
|
|
247
303
|
}
|
|
248
304
|
};
|
|
249
305
|
OxMenuPart.styles = [
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ox-menu-part.js","sourceRoot":"","sources":["../../src/ox-menu-part.ts"],"names":[],"mappings":"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","sourcesContent":["import '@material/web/icon/icon.js'\nimport './ox-menu-portrait.js'\nimport './ox-menu-landscape.js'\n\nimport { css, html, LitElement } from 'lit'\nimport { customElement, property, state } from 'lit/decorators.js'\nimport { RouteController, MenuController } from '@operato/shell'\nimport { ScrollbarStyles } from '@operato/styles'\n\nimport { Menu } from './types.js'\nimport { resolveActive, joinedPath, type UserOpened } from './menu-active.js'\nimport { shouldAutoCollapse } from './menu-mode.js'\n\n@customElement('ox-menu-part')\nexport class OxMenuPart extends LitElement {\n static styles = [\n ScrollbarStyles,\n css`\n :host {\n display: flex;\n overflow-y: auto;\n flex-direction: column;\n height: 100%;\n min-width: 200px;\n background-color: var(--md-sys-color-surface);\n }\n\n :host([landscape]) {\n overflow-x: auto;\n flex-direction: row;\n width: 100%;\n min-height: 20px;\n }\n\n /*\n * 레일 모드 — 폭의 주인이 여기다.\n *\n * min-width: 200px 이 접힘을 막는다. 안쪽(ox-menu-portrait)만 줄여도 겉이 200px 로 남아\n * 아이콘 옆이 빈 칸이 된다. 그래서 접힘 상태를 이 요소가 갖는다 — 셸이 크기를 재는 요소도\n * 이것이고, 리사이즈 손잡이가 인라인 폭을 쓰는 요소도 이것이다.\n *\n * 가로만 감춘다. overflow: hidden 으로 두면 **긴 메뉴의 아래쪽에 갈 수 없다** — 이 앱의\n * 메뉴는 최상위가 일곱이고 그 아래가 더 있다.\n */\n :host([rail]) {\n min-width: 0;\n width: var(--menu-rail-width, 208px);\n overflow-x: hidden;\n overflow-y: auto;\n }\n\n :host([rail][collapsed]) {\n width: var(--menu-rail-width-collapsed, 56px);\n }\n\n ox-menu-portrait,\n ox-menu-landscape {\n flex: 1;\n }\n `\n ]\n\n private _route = new RouteController(this)\n private _menu = new MenuController(this)\n\n @property({ type: String }) orientation?: 'landscape' | 'portrait'\n\n /**\n * 레일 모드 — 접을 수 있고, 접히면 하위가 팝업으로 뜬다.\n *\n * **옵트인이다.** 켜지 않은 앱(dataset · ecs · gangsters · pms)은 지금과 한 글자도 다르지 않다.\n *\n * 모양이 함께 바뀌는 것은 취향이 아니라 요건이다. 기존 모양은 줄마다 폭 전체를 쓰는 밑줄이 있고\n * 왼쪽 여백이 16px 이라 56px 에서 무너진다(§`menu-rail-styles.ts`).\n *\n * 가로 메뉴(`landscape`)에는 뜻이 없다 — 접을 축이 다르다. 그때는 무시한다.\n */\n @property({ type: Boolean, reflect: true }) rail = false\n\n /** 접힘 — 아이콘만. 사용자가 고른 것은 브라우저에 남긴다. */\n @property({ type: Boolean, reflect: true }) collapsed = false\n\n /**\n * 이 메뉴가 **서랍**인가 — 화면 위에 겹쳐 떴다가 닫히는 상태.\n *\n * 메뉴를 감추는 설정을 켰거나 휴대폰이면 그렇다(`lite-menu` 가 판단해 준다).\n *\n * ── 왜 알아야 하나 ──────────────────────────────────────────────────────\n * 최상위 항목은 자기 대표 화면의 주소를 갖는다. 늘 보이는 메뉴에서는 그것이 도움이 된다 —\n * 누르면 어디든 도착한다.\n *\n * 그런데 서랍에서는 **누르는 순간 서랍이 닫힌다.** 그러면 그 묶음의 나머지 하위를 볼 기회가\n * 없다. 휴대폰에서 「작업」을 누르면 작업지시로 튀고 나머지 다섯은 못 본다.\n *\n * 그래서 서랍에서는 최상위가 **펼치기만** 한다. 하위를 눌러야 이동한다.\n */\n @property({ type: Boolean, reflect: true }) drawer = false\n\n private static readonly LS_KEY = 'ox-menu.collapsed'\n\n /**\n * 손잡이가 박아 둔 인라인 폭.\n *\n * `ox-nav-bar` 의 리사이즈 손잡이는 `div[navbar]` 의 **자식에 직접** `style.width` 를 쓴다\n * (`ox-nav-bar.ts` 의 `resizeDrag`). 인라인 값은 이 컴포넌트의 `:host([collapsed])` 규칙을\n * 이기므로, **한 번이라도 폭을 끌어 놓은 사용자는 접어도 폭이 그대로다.** 접을 때 치우고 펼칠 때\n * 되돌린다.\n */\n private _draggedWidth: string | null = null\n\n private _watchWidth?: ResizeObserver\n\n connectedCallback() {\n super.connectedCallback()\n\n try {\n this.collapsed = localStorage.getItem(OxMenuPart.LS_KEY) === '1'\n } catch {\n /* 막힐 수 있다(사생활 보호 모드). 펼쳐진 상태가 안전한 기본이다. */\n }\n\n /*\n * 손잡이로 좁게 끌면 스스로 접는다.\n *\n * 폭을 재는 자리는 여기다 — 손잡이는 이 요소에 인라인 폭을 쓴다. 판정은\n * `shouldAutoCollapse` 가 하고(시험으로 못박혀 있다) 여기서는 폭을 넘기고 결과를 따른다.\n */\n if (typeof ResizeObserver !== 'undefined') {\n this._watchWidth = new ResizeObserver(() => this.collapseIfTooNarrow())\n this._watchWidth.observe(this)\n }\n }\n\n disconnectedCallback() {\n super.disconnectedCallback()\n this._watchWidth?.disconnect()\n }\n\n /**\n * 너무 좁아졌으면 접는다.\n *\n * ── 끌어 놓은 폭을 기억하지 않는다 ──────────────────────────────────────\n * 손으로 접을 때는 그 폭을 적어 두고 펼칠 때 되돌린다. 그런데 **스스로 접은 경우에는 그\n * 폭이 「너무 좁은 폭」이다.** 되돌리면 펼치는 순간 다시 접혀 아무리 눌러도 펼쳐지지 않는다.\n */\n private collapseIfTooNarrow() {\n if (!this.rail) {\n return\n }\n\n /*\n * ── 접힌 뒤에는 손잡이가 더 써도 듣지 않는다 ──────────────────────────\n * 손잡이는 **끄는 동안 매 이동마다** 인라인 폭을 쓴다(`ox-nav-bar` 의 `resizeDrag`). 그래서\n * 접으면서 그 폭을 한 번 치워도 다음 이동이 곧바로 다시 쓴다 — 접힌 채로 폭이 계속 따라와\n * 「접혔는데 안 접힌」 상태가 된다.\n *\n * 들어오는 대로 치운다. 사용자가 손을 떼지 않아도 그 자리에서 멈춘다.\n */\n if (this.collapsed) {\n if (this.style.width) {\n this.style.width = ''\n }\n\n return\n }\n\n if (!shouldAutoCollapse(this.offsetWidth, { rail: true, collapsed: false })) {\n return\n }\n\n /*\n * 스스로 접을 때는 끌어 놓은 폭을 **기억하지 않는다.** 그 폭이 「너무 좁은 폭」이므로,\n * 되돌리면 펼치는 순간 다시 접혀 아무리 눌러도 펼쳐지지 않는다.\n */\n this._draggedWidth = null\n this.style.width = ''\n this.setCollapsed(true)\n }\n\n /**\n * 접혔으면 폭 손잡이를 감춘다.\n *\n * ── 왜 감추나 ──────────────────────────────────────────────────────────\n * 아이콘만 남은 레일의 폭을 바꿀 이유가 없다. 그리고 **끌면 실제로 망가진다** — 손잡이는\n * 인라인 `style.width` 를 쓰고(`ox-nav-bar` 의 `resizeDrag`), 인라인은 이 컴포넌트의\n * `:host([collapsed])` 규칙을 이긴다. 그래서 접힌 레일을 300px 로 늘려 빈 칸을 만들 수 있다.\n *\n * ── 왜 남의 요소를 건드리나 ────────────────────────────────────────────\n * 손잡이는 `ox-nav-bar` 가 `div[navbar]` 의 **형제**로 그린다. 그 층은 안에 무엇이 들었는지\n * 모르므로 접힘을 알 수 없다. 접힘을 아는 쪽은 여기다. 이미 손잡이가 쓴 인라인 폭을 여기서\n * 치우고 있으므로, 그 짝을 같은 자리에 둔다.\n */\n private syncSplitter() {\n if (!this.rail) {\n return\n }\n\n const splitter = this.parentElement?.nextElementSibling as HTMLElement | undefined\n\n if (splitter?.tagName?.toLowerCase() === 'ox-resize-splitter') {\n splitter.hidden = this.collapsed\n }\n }\n\n protected updated() {\n this.syncSplitter()\n }\n\n /**\n * 접기/펼치기.\n *\n * 폭이 바뀌면 캔버스(씬)와 그리스트가 자기 크기를 다시 재야 하므로 `resize` 를 낸다.\n * `twin-nav-rail` 이 같은 자리에서 같은 일을 한다.\n */\n private toggleCollapsed() {\n const next = !this.collapsed\n\n if (next) {\n if (this.style.width) {\n this._draggedWidth = this.style.width\n this.style.width = ''\n }\n } else if (this._draggedWidth) {\n this.style.width = this._draggedWidth\n this._draggedWidth = null\n }\n\n this.setCollapsed(next)\n }\n\n /** 접힘을 정하고 남긴다. 스스로 접을 때와 손으로 접을 때가 이 뒷부분을 함께 쓴다. */\n private setCollapsed(next: boolean) {\n this.collapsed = next\n\n try {\n localStorage.setItem(OxMenuPart.LS_KEY, next ? '1' : '0')\n } catch {\n /* 못 남기면 다음에 펼쳐진 채로 뜬다. 기능은 그대로다. */\n }\n\n window.dispatchEvent(new Event('resize'))\n }\n\n @state() _activeTopLevel?: Menu\n @state() _activeMenu?: Menu\n @state() _path?: string\n\n /**\n * 사용자가 손으로 정한 펼침.\n *\n * ```\n * undefined 정한 적 없다 — 주소가 가리키는 것을 쓴다\n * null 닫았다\n * Menu 그 묶음을 열었다\n * ```\n *\n * ── 왜 이 값이 필요한가 ─────────────────────────────────────────────────\n * 펼침 상태는 **주소에서 계산된다** — `willUpdate` 가 매 렌더마다 `findActivePage()` 를 불러\n * `_activeTopLevel` 을 주소가 가리키는 메뉴로 다시 넣는다. 그래서 손으로 무엇을 열거나 닫아도\n * **그리기 전에 되돌아왔다.** 여닫는 코드는 처음부터 있었는데 한 번도 살아 있지 못했다.\n *\n * 다른 최상위를 누를 때 열리는 것처럼 보였던 것은 여닫힘이 아니라 **이동**이었다. 링크가 주소를\n * 바꾸고, 바뀐 주소로 다른 메뉴가 계산된 것이다. 그래서 화살표만 눌러 이동 없이 열려고 하면\n * 아무 일도 안 일어났다.\n *\n * 한때 「닫았다」만 기억했다. 그러면 접기는 되지만 **다른 묶음을 열 수는 없었다** — 그 방향도\n * 같은 이유로 되돌려진다. 세 값으로 두면 두 방향이 다 산다.\n *\n * **주소가 바뀌면 잊는다** — 새 자리에서는 그 자리의 메뉴가 열려 있어야 한다.\n */\n private _userOpened: UserOpened\n\n render() {\n const menus = this._menu.menus\n\n return html`\n <slot name=\"head\"></slot>\n ${this.orientation !== 'landscape'\n ? html`<ox-menu-portrait\n .menus=${menus}\n .activeTopLevel=${this._activeTopLevel}\n .activeMenu=${this._activeMenu}\n .path=${this._path}\n ?rail=${this.rail}\n ?collapsed=${this.rail && this.collapsed}\n ?drawer=${this.drawer}\n ></ox-menu-portrait>`\n : html`<ox-menu-landscape\n .menus=${menus}\n .activeTopLevel=${this._activeTopLevel}\n .activeMenu=${this._activeMenu}\n .path=${this._path}\n ></ox-menu-landscape>`}\n <slot name=\"tail\"></slot>\n `\n }\n\n firstUpdated() {\n this.renderRoot.addEventListener('active-toplevel', (e: Event) => {\n e.stopPropagation()\n e.preventDefault()\n\n /*\n * **`detail: undefined` 는 `null` 로 도착한다.**\n *\n * `CustomEvent` 의 `detail` 은 기본값이 `null` 이라, 보내는 쪽이 `undefined` 를 넣어도 받는\n * 쪽에서는 `null` 이다. 그래서 `next === undefined` 로 「접겠다는 뜻」을 가리면 한 번도 맞지\n * 않는다 — 값이 있나 없나로 가른다.\n */\n const next = (e as CustomEvent).detail || undefined\n\n /* 열었든 닫았든 손으로 정한 것을 적어 둔다 — 안 적으면 주소가 곧 되돌린다. */\n this._userOpened = next ?? null\n this._activeTopLevel = next\n })\n\n this.renderRoot.addEventListener('collapse-toggle', (e: Event) => {\n e.stopPropagation()\n this.toggleCollapsed()\n })\n }\n\n protected willUpdate() {\n this.findActivePage()\n\n if (this.orientation == 'portrait') {\n this.removeAttribute('landscape')\n } else if (this.orientation == 'landscape') {\n this.setAttribute('landscape', '')\n }\n\n const { slotTemplate } = this._menu\n if (slotTemplate) {\n this.replaceChild(slotTemplate, this.renderRoot)\n }\n }\n\n /**\n * 지금 자리의 메뉴를 다시 정한다.\n *\n * 규칙은 `menu-active.ts` 에 있다 — 순수 함수라 시험으로 못박혀 있다. 여기서는 라우터가 준\n * 것을 넘기고 결과를 상태에 넣는 일만 한다.\n */\n private findActivePage() {\n const { page, resourceId, context } = this._route\n const where = { page, resourceId, context }\n const path = joinedPath(where)\n\n /* 자리를 옮겼으면 손으로 정한 것을 잊는다 — 새 자리에서는 그 자리의 메뉴가 열려 있어야 한다. */\n if (this._path !== undefined && path !== this._path) {\n this._userOpened = undefined\n }\n\n const { top, menu } = resolveActive(this._menu.menus || [], where, this._userOpened)\n\n this._path = path\n this._activeTopLevel = top\n this._activeMenu = menu\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"ox-menu-part.js","sourceRoot":"","sources":["../../src/ox-menu-part.ts"],"names":[],"mappings":"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","sourcesContent":["import '@material/web/icon/icon.js'\nimport './ox-menu-portrait.js'\nimport './ox-menu-landscape.js'\n\nimport { css, html, LitElement } from 'lit'\nimport { customElement, property, state } from 'lit/decorators.js'\nimport { RouteController, MenuController } from '@operato/shell'\nimport { ScrollbarStyles } from '@operato/styles'\n\nimport { Menu } from './types.js'\nimport { resolveActive, joinedPath, type UserOpened } from './menu-active.js'\nimport { pageAsksRailCollapsed, railCollapsed, shouldAutoCollapse } from './menu-mode.js'\n\n@customElement('ox-menu-part')\nexport class OxMenuPart extends LitElement {\n static styles = [\n ScrollbarStyles,\n css`\n :host {\n display: flex;\n overflow-y: auto;\n flex-direction: column;\n height: 100%;\n min-width: 200px;\n background-color: var(--md-sys-color-surface);\n }\n\n :host([landscape]) {\n overflow-x: auto;\n flex-direction: row;\n width: 100%;\n min-height: 20px;\n }\n\n /*\n * 레일 모드 — 폭의 주인이 여기다.\n *\n * min-width: 200px 이 접힘을 막는다. 안쪽(ox-menu-portrait)만 줄여도 겉이 200px 로 남아\n * 아이콘 옆이 빈 칸이 된다. 그래서 접힘 상태를 이 요소가 갖는다 — 셸이 크기를 재는 요소도\n * 이것이고, 리사이즈 손잡이가 인라인 폭을 쓰는 요소도 이것이다.\n *\n * 가로만 감춘다. overflow: hidden 으로 두면 **긴 메뉴의 아래쪽에 갈 수 없다** — 이 앱의\n * 메뉴는 최상위가 일곱이고 그 아래가 더 있다.\n */\n :host([rail]) {\n min-width: 0;\n width: var(--menu-rail-width, 208px);\n overflow-x: hidden;\n overflow-y: auto;\n }\n\n :host([rail][collapsed]) {\n width: var(--menu-rail-width-collapsed, 56px);\n }\n\n ox-menu-portrait,\n ox-menu-landscape {\n flex: 1;\n }\n `\n ]\n\n private _route = new RouteController(this)\n private _menu = new MenuController(this)\n\n @property({ type: String }) orientation?: 'landscape' | 'portrait'\n\n /**\n * 레일 모드 — 접을 수 있고, 접히면 하위가 팝업으로 뜬다.\n *\n * **옵트인이다.** 켜지 않은 앱(dataset · ecs · gangsters · pms)은 지금과 한 글자도 다르지 않다.\n *\n * 모양이 함께 바뀌는 것은 취향이 아니라 요건이다. 기존 모양은 줄마다 폭 전체를 쓰는 밑줄이 있고\n * 왼쪽 여백이 16px 이라 56px 에서 무너진다(§`menu-rail-styles.ts`).\n *\n * 가로 메뉴(`landscape`)에는 뜻이 없다 — 접을 축이 다르다. 그때는 무시한다.\n */\n @property({ type: Boolean, reflect: true }) rail = false\n\n /**\n * 접힘 — 아이콘만. 사용자가 고른 것은 브라우저에 남긴다.\n *\n * This is the state the rail stands in now. What the person chose is `_storedCollapsed`; a page that asks for the\n * rail to fold changes this value only while it is open (`railCollapsed`).\n */\n @property({ type: Boolean, reflect: true }) collapsed = false\n\n /** What the person chose, kept in the browser. A page's request never writes here. */\n private _storedCollapsed = false\n\n /** The open page asked for the rail to fold (`context.rail`). */\n private _pageAsks = false\n\n /** What the person chose on the page that asked, while it is open. Forgotten when the page changes. */\n private _onPage?: boolean\n\n /** The page the two values above were read on. */\n private _railPage?: string\n\n /**\n * 이 메뉴가 **서랍**인가 — 화면 위에 겹쳐 떴다가 닫히는 상태.\n *\n * 메뉴를 감추는 설정을 켰거나 휴대폰이면 그렇다(`lite-menu` 가 판단해 준다).\n *\n * ── 왜 알아야 하나 ──────────────────────────────────────────────────────\n * 최상위 항목은 자기 대표 화면의 주소를 갖는다. 늘 보이는 메뉴에서는 그것이 도움이 된다 —\n * 누르면 어디든 도착한다.\n *\n * 그런데 서랍에서는 **누르는 순간 서랍이 닫힌다.** 그러면 그 묶음의 나머지 하위를 볼 기회가\n * 없다. 휴대폰에서 「작업」을 누르면 작업지시로 튀고 나머지 다섯은 못 본다.\n *\n * 그래서 서랍에서는 최상위가 **펼치기만** 한다. 하위를 눌러야 이동한다.\n */\n @property({ type: Boolean, reflect: true }) drawer = false\n\n private static readonly LS_KEY = 'ox-menu.collapsed'\n\n /**\n * 손잡이가 박아 둔 인라인 폭.\n *\n * `ox-nav-bar` 의 리사이즈 손잡이는 `div[navbar]` 의 **자식에 직접** `style.width` 를 쓴다\n * (`ox-nav-bar.ts` 의 `resizeDrag`). 인라인 값은 이 컴포넌트의 `:host([collapsed])` 규칙을\n * 이기므로, **한 번이라도 폭을 끌어 놓은 사용자는 접어도 폭이 그대로다.** 접을 때 치우고 펼칠 때\n * 되돌린다.\n */\n private _draggedWidth: string | null = null\n\n private _watchWidth?: ResizeObserver\n\n connectedCallback() {\n super.connectedCallback()\n\n try {\n this._storedCollapsed = localStorage.getItem(OxMenuPart.LS_KEY) === '1'\n this.collapsed = this._storedCollapsed\n } catch {\n /* 막힐 수 있다(사생활 보호 모드). 펼쳐진 상태가 안전한 기본이다. */\n }\n\n /*\n * 손잡이로 좁게 끌면 스스로 접는다.\n *\n * 폭을 재는 자리는 여기다 — 손잡이는 이 요소에 인라인 폭을 쓴다. 판정은\n * `shouldAutoCollapse` 가 하고(시험으로 못박혀 있다) 여기서는 폭을 넘기고 결과를 따른다.\n */\n if (typeof ResizeObserver !== 'undefined') {\n this._watchWidth = new ResizeObserver(() => this.collapseIfTooNarrow())\n this._watchWidth.observe(this)\n }\n }\n\n disconnectedCallback() {\n super.disconnectedCallback()\n this._watchWidth?.disconnect()\n }\n\n /**\n * 너무 좁아졌으면 접는다.\n *\n * ── 끌어 놓은 폭을 기억하지 않는다 ──────────────────────────────────────\n * 손으로 접을 때는 그 폭을 적어 두고 펼칠 때 되돌린다. 그런데 **스스로 접은 경우에는 그\n * 폭이 「너무 좁은 폭」이다.** 되돌리면 펼치는 순간 다시 접혀 아무리 눌러도 펼쳐지지 않는다.\n */\n private collapseIfTooNarrow() {\n if (!this.rail) {\n return\n }\n\n /*\n * ── 접힌 뒤에는 손잡이가 더 써도 듣지 않는다 ──────────────────────────\n * 손잡이는 **끄는 동안 매 이동마다** 인라인 폭을 쓴다(`ox-nav-bar` 의 `resizeDrag`). 그래서\n * 접으면서 그 폭을 한 번 치워도 다음 이동이 곧바로 다시 쓴다 — 접힌 채로 폭이 계속 따라와\n * 「접혔는데 안 접힌」 상태가 된다.\n *\n * 들어오는 대로 치운다. 사용자가 손을 떼지 않아도 그 자리에서 멈춘다.\n */\n if (this.collapsed) {\n if (this.style.width) {\n this.style.width = ''\n }\n\n return\n }\n\n if (!shouldAutoCollapse(this.offsetWidth, { rail: true, collapsed: false })) {\n return\n }\n\n /*\n * 스스로 접을 때는 끌어 놓은 폭을 **기억하지 않는다.** 그 폭이 「너무 좁은 폭」이므로,\n * 되돌리면 펼치는 순간 다시 접혀 아무리 눌러도 펼쳐지지 않는다.\n */\n this._draggedWidth = null\n this.style.width = ''\n this.setCollapsed(true)\n }\n\n /**\n * 접혔으면 폭 손잡이를 감춘다.\n *\n * ── 왜 감추나 ──────────────────────────────────────────────────────────\n * 아이콘만 남은 레일의 폭을 바꿀 이유가 없다. 그리고 **끌면 실제로 망가진다** — 손잡이는\n * 인라인 `style.width` 를 쓰고(`ox-nav-bar` 의 `resizeDrag`), 인라인은 이 컴포넌트의\n * `:host([collapsed])` 규칙을 이긴다. 그래서 접힌 레일을 300px 로 늘려 빈 칸을 만들 수 있다.\n *\n * ── 왜 남의 요소를 건드리나 ────────────────────────────────────────────\n * 손잡이는 `ox-nav-bar` 가 `div[navbar]` 의 **형제**로 그린다. 그 층은 안에 무엇이 들었는지\n * 모르므로 접힘을 알 수 없다. 접힘을 아는 쪽은 여기다. 이미 손잡이가 쓴 인라인 폭을 여기서\n * 치우고 있으므로, 그 짝을 같은 자리에 둔다.\n */\n private syncSplitter() {\n if (!this.rail) {\n return\n }\n\n const splitter = this.parentElement?.nextElementSibling as HTMLElement | undefined\n\n if (splitter?.tagName?.toLowerCase() === 'ox-resize-splitter') {\n splitter.hidden = this.collapsed\n }\n }\n\n protected updated(changed: Map<PropertyKey, unknown>) {\n this.syncSplitter()\n\n /*\n * A page's request changes the width without a click, so the canvas has to measure again the same way it does\n * after a toggle. The first render is not a change -- `changed` then holds the initial `undefined`.\n */\n if (changed.has('collapsed') && changed.get('collapsed') !== undefined) {\n window.dispatchEvent(new Event('resize'))\n }\n }\n\n /**\n * 접기/펼치기.\n *\n * 폭이 바뀌면 캔버스(씬)와 그리스트가 자기 크기를 다시 재야 하므로 `resize` 를 낸다.\n * `twin-nav-rail` 이 같은 자리에서 같은 일을 한다.\n */\n private toggleCollapsed() {\n const next = !this.collapsed\n\n if (next) {\n if (this.style.width) {\n this._draggedWidth = this.style.width\n this.style.width = ''\n }\n } else if (this._draggedWidth) {\n this.style.width = this._draggedWidth\n this._draggedWidth = null\n }\n\n this.setCollapsed(next)\n }\n\n /**\n * 접힘을 정하고 남긴다. 스스로 접을 때와 손으로 접을 때가 이 뒷부분을 함께 쓴다.\n *\n * On a page that asked for the rail to fold, the choice lasts while that page is open and is not stored -- leaving\n * brings back what the person chose before. The resize event goes out from `updated`.\n */\n private setCollapsed(next: boolean) {\n if (this._pageAsks) {\n this._onPage = next\n } else {\n this._storedCollapsed = next\n\n try {\n localStorage.setItem(OxMenuPart.LS_KEY, next ? '1' : '0')\n } catch {\n /* 못 남기면 다음에 펼쳐진 채로 뜬다. 기능은 그대로다. */\n }\n }\n\n this.collapsed = next\n }\n\n @state() _activeTopLevel?: Menu\n @state() _activeMenu?: Menu\n @state() _path?: string\n\n /**\n * 사용자가 손으로 정한 펼침.\n *\n * ```\n * undefined 정한 적 없다 — 주소가 가리키는 것을 쓴다\n * null 닫았다\n * Menu 그 묶음을 열었다\n * ```\n *\n * ── 왜 이 값이 필요한가 ─────────────────────────────────────────────────\n * 펼침 상태는 **주소에서 계산된다** — `willUpdate` 가 매 렌더마다 `findActivePage()` 를 불러\n * `_activeTopLevel` 을 주소가 가리키는 메뉴로 다시 넣는다. 그래서 손으로 무엇을 열거나 닫아도\n * **그리기 전에 되돌아왔다.** 여닫는 코드는 처음부터 있었는데 한 번도 살아 있지 못했다.\n *\n * 다른 최상위를 누를 때 열리는 것처럼 보였던 것은 여닫힘이 아니라 **이동**이었다. 링크가 주소를\n * 바꾸고, 바뀐 주소로 다른 메뉴가 계산된 것이다. 그래서 화살표만 눌러 이동 없이 열려고 하면\n * 아무 일도 안 일어났다.\n *\n * 한때 「닫았다」만 기억했다. 그러면 접기는 되지만 **다른 묶음을 열 수는 없었다** — 그 방향도\n * 같은 이유로 되돌려진다. 세 값으로 두면 두 방향이 다 산다.\n *\n * **주소가 바뀌면 잊는다** — 새 자리에서는 그 자리의 메뉴가 열려 있어야 한다.\n */\n private _userOpened: UserOpened\n\n render() {\n const menus = this._menu.menus\n\n return html`\n <slot name=\"head\"></slot>\n ${this.orientation !== 'landscape'\n ? html`<ox-menu-portrait\n .menus=${menus}\n .activeTopLevel=${this._activeTopLevel}\n .activeMenu=${this._activeMenu}\n .path=${this._path}\n ?rail=${this.rail}\n ?collapsed=${this.rail && this.collapsed}\n ?drawer=${this.drawer}\n ></ox-menu-portrait>`\n : html`<ox-menu-landscape\n .menus=${menus}\n .activeTopLevel=${this._activeTopLevel}\n .activeMenu=${this._activeMenu}\n .path=${this._path}\n ></ox-menu-landscape>`}\n <slot name=\"tail\"></slot>\n `\n }\n\n firstUpdated() {\n this.renderRoot.addEventListener('active-toplevel', (e: Event) => {\n e.stopPropagation()\n e.preventDefault()\n\n /*\n * **`detail: undefined` 는 `null` 로 도착한다.**\n *\n * `CustomEvent` 의 `detail` 은 기본값이 `null` 이라, 보내는 쪽이 `undefined` 를 넣어도 받는\n * 쪽에서는 `null` 이다. 그래서 `next === undefined` 로 「접겠다는 뜻」을 가리면 한 번도 맞지\n * 않는다 — 값이 있나 없나로 가른다.\n */\n const next = (e as CustomEvent).detail || undefined\n\n /* 열었든 닫았든 손으로 정한 것을 적어 둔다 — 안 적으면 주소가 곧 되돌린다. */\n this._userOpened = next ?? null\n this._activeTopLevel = next\n })\n\n this.renderRoot.addEventListener('collapse-toggle', (e: Event) => {\n e.stopPropagation()\n this.toggleCollapsed()\n })\n }\n\n protected willUpdate() {\n this.findActivePage()\n\n if (this.orientation == 'portrait') {\n this.removeAttribute('landscape')\n } else if (this.orientation == 'landscape') {\n this.setAttribute('landscape', '')\n }\n\n const { slotTemplate } = this._menu\n if (slotTemplate) {\n this.replaceChild(slotTemplate, this.renderRoot)\n }\n }\n\n /**\n * 지금 자리의 메뉴를 다시 정한다.\n *\n * 규칙은 `menu-active.ts` 에 있다 — 순수 함수라 시험으로 못박혀 있다. 여기서는 라우터가 준\n * 것을 넘기고 결과를 상태에 넣는 일만 한다.\n */\n private findActivePage() {\n const { page, resourceId, context } = this._route\n const where = { page, resourceId, context }\n const path = joinedPath(where)\n\n /* 자리를 옮겼으면 손으로 정한 것을 잊는다 — 새 자리에서는 그 자리의 메뉴가 열려 있어야 한다. */\n if (this._path !== undefined && path !== this._path) {\n this._userOpened = undefined\n }\n\n const { top, menu } = resolveActive(this._menu.menus || [], where, this._userOpened)\n\n this._path = path\n this._activeTopLevel = top\n this._activeMenu = menu\n\n this.applyPageRail(page, context)\n }\n\n /**\n * Fold the rail while the open page asks for it, and bring back the person's choice when it no longer does.\n *\n * The rule is `railCollapsed` in `menu-mode.ts`. A toggle made on the asking page is forgotten when the page\n * changes, so the next visit folds again.\n */\n private applyPageRail(page: string, context?: { rail?: string } | null) {\n const pageAsks = this.rail && pageAsksRailCollapsed(context)\n\n if (page !== this._railPage || pageAsks !== this._pageAsks) {\n this._onPage = undefined\n }\n\n this._railPage = page\n this._pageAsks = pageAsks\n\n const next = railCollapsed({ stored: this._storedCollapsed, pageAsks, onPage: this._onPage })\n\n if (next === this.collapsed) {\n return\n }\n\n /* Folding clears a width dragged with the handle, the same as a toggle; unfolding gives it back. */\n if (next && this.style.width) {\n this._draggedWidth = this.style.width\n this.style.width = ''\n } else if (!next && this._draggedWidth) {\n this.style.width = this._draggedWidth\n this._draggedWidth = null\n }\n\n this.collapsed = next\n }\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import '../src/ox-menu-part.js';
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { html } from 'lit';
|
|
2
|
+
import { fixture, expect, nextFrame } from '@open-wc/testing';
|
|
3
|
+
import { store, UPDATE_PAGE, UPDATE_CONTEXT } from '@operato/shell';
|
|
4
|
+
import '../src/ox-menu-part.js';
|
|
5
|
+
import { pageAsksRailCollapsed, railCollapsed } from '../src/menu-mode.js';
|
|
6
|
+
/**
|
|
7
|
+
* A page can fold the rail while it is open (`context.rail: 'collapsed'`) and the person's own choice comes back
|
|
8
|
+
* when it closes. The pure rule is checked first, then the wiring in `ox-menu-part`.
|
|
9
|
+
*/
|
|
10
|
+
describe('a page folds the rail while it is open', () => {
|
|
11
|
+
describe('the rule', () => {
|
|
12
|
+
it('without a request the stored choice stands', () => {
|
|
13
|
+
expect(railCollapsed({ stored: false, pageAsks: false })).to.equal(false);
|
|
14
|
+
expect(railCollapsed({ stored: true, pageAsks: false })).to.equal(true);
|
|
15
|
+
});
|
|
16
|
+
it('a request folds the rail whatever was stored', () => {
|
|
17
|
+
expect(railCollapsed({ stored: false, pageAsks: true })).to.equal(true);
|
|
18
|
+
expect(railCollapsed({ stored: true, pageAsks: true })).to.equal(true);
|
|
19
|
+
});
|
|
20
|
+
it('a toggle on the asking page wins while that page is open', () => {
|
|
21
|
+
expect(railCollapsed({ stored: false, pageAsks: true, onPage: false })).to.equal(false);
|
|
22
|
+
});
|
|
23
|
+
it('a toggle on the page is ignored once the page no longer asks', () => {
|
|
24
|
+
expect(railCollapsed({ stored: false, pageAsks: false, onPage: true })).to.equal(false);
|
|
25
|
+
});
|
|
26
|
+
it('only the declared value is a request', () => {
|
|
27
|
+
expect(pageAsksRailCollapsed({ rail: 'collapsed' })).to.equal(true);
|
|
28
|
+
expect(pageAsksRailCollapsed({ rail: 'open' })).to.equal(false);
|
|
29
|
+
expect(pageAsksRailCollapsed({})).to.equal(false);
|
|
30
|
+
expect(pageAsksRailCollapsed(undefined)).to.equal(false);
|
|
31
|
+
expect(pageAsksRailCollapsed(null)).to.equal(false);
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
describe('ox-menu-part', () => {
|
|
35
|
+
const KEY = 'ox-menu.collapsed';
|
|
36
|
+
function go(page, context) {
|
|
37
|
+
store.dispatch({ type: UPDATE_PAGE, page, resourceId: '', params: {} });
|
|
38
|
+
store.dispatch({ type: UPDATE_CONTEXT, context });
|
|
39
|
+
}
|
|
40
|
+
async function settle(el) {
|
|
41
|
+
await el.updateComplete;
|
|
42
|
+
await nextFrame();
|
|
43
|
+
await el.updateComplete;
|
|
44
|
+
}
|
|
45
|
+
async function mount(stored) {
|
|
46
|
+
localStorage.setItem(KEY, stored);
|
|
47
|
+
go('list-page', { title: 'list' });
|
|
48
|
+
const el = (await fixture(html `<ox-menu-part rail></ox-menu-part>`));
|
|
49
|
+
await settle(el);
|
|
50
|
+
return el;
|
|
51
|
+
}
|
|
52
|
+
afterEach(() => {
|
|
53
|
+
localStorage.removeItem(KEY);
|
|
54
|
+
go('list-page', { title: 'list' });
|
|
55
|
+
});
|
|
56
|
+
it('folds on the asking page and opens again on leaving, without touching the stored choice', async () => {
|
|
57
|
+
const el = await mount('0');
|
|
58
|
+
expect(el.collapsed).to.equal(false);
|
|
59
|
+
go('modeller-page', { title: 'modeller', rail: 'collapsed' });
|
|
60
|
+
await settle(el);
|
|
61
|
+
expect(el.collapsed).to.equal(true);
|
|
62
|
+
expect(el.hasAttribute('collapsed')).to.equal(true);
|
|
63
|
+
expect(localStorage.getItem(KEY)).to.equal('0');
|
|
64
|
+
go('list-page', { title: 'list' });
|
|
65
|
+
await settle(el);
|
|
66
|
+
expect(el.collapsed).to.equal(false);
|
|
67
|
+
});
|
|
68
|
+
it('a rail the person folded stays folded after leaving the asking page', async () => {
|
|
69
|
+
const el = await mount('1');
|
|
70
|
+
go('modeller-page', { title: 'modeller', rail: 'collapsed' });
|
|
71
|
+
await settle(el);
|
|
72
|
+
go('list-page', { title: 'list' });
|
|
73
|
+
await settle(el);
|
|
74
|
+
expect(el.collapsed).to.equal(true);
|
|
75
|
+
});
|
|
76
|
+
it('opening it by hand on the asking page is not stored and the next visit folds again', async () => {
|
|
77
|
+
const el = await mount('0');
|
|
78
|
+
go('modeller-page', { title: 'modeller', rail: 'collapsed' });
|
|
79
|
+
await settle(el);
|
|
80
|
+
el.renderRoot.querySelector('ox-menu-portrait').dispatchEvent(new CustomEvent('collapse-toggle', { bubbles: true, composed: true }));
|
|
81
|
+
await settle(el);
|
|
82
|
+
expect(el.collapsed).to.equal(false);
|
|
83
|
+
expect(localStorage.getItem(KEY)).to.equal('0');
|
|
84
|
+
go('list-page', { title: 'list' });
|
|
85
|
+
await settle(el);
|
|
86
|
+
go('modeller-page', { title: 'modeller', rail: 'collapsed' });
|
|
87
|
+
await settle(el);
|
|
88
|
+
expect(el.collapsed).to.equal(true);
|
|
89
|
+
});
|
|
90
|
+
it('a menu without the rail does not fold for the request', async () => {
|
|
91
|
+
localStorage.setItem(KEY, '0');
|
|
92
|
+
const el = (await fixture(html `<ox-menu-part></ox-menu-part>`));
|
|
93
|
+
go('modeller-page', { title: 'modeller', rail: 'collapsed' });
|
|
94
|
+
await settle(el);
|
|
95
|
+
expect(el.hasAttribute('collapsed')).to.equal(false);
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
//# sourceMappingURL=menu-rail-page.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"menu-rail-page.test.js","sourceRoot":"","sources":["../../test/menu-rail-page.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { html } from 'lit'\nimport { fixture, expect, nextFrame } from '@open-wc/testing'\nimport { store, UPDATE_PAGE, UPDATE_CONTEXT } from '@operato/shell'\n\nimport '../src/ox-menu-part.js'\nimport { OxMenuPart } from '../src/ox-menu-part.js'\nimport { pageAsksRailCollapsed, railCollapsed } from '../src/menu-mode.js'\n\n/**\n * A page can fold the rail while it is open (`context.rail: 'collapsed'`) and the person's own choice comes back\n * when it closes. The pure rule is checked first, then the wiring in `ox-menu-part`.\n */\ndescribe('a page folds the rail while it is open', () => {\n describe('the rule', () => {\n it('without a request the stored choice stands', () => {\n expect(railCollapsed({ stored: false, pageAsks: false })).to.equal(false)\n expect(railCollapsed({ stored: true, pageAsks: false })).to.equal(true)\n })\n\n it('a request folds the rail whatever was stored', () => {\n expect(railCollapsed({ stored: false, pageAsks: true })).to.equal(true)\n expect(railCollapsed({ stored: true, pageAsks: true })).to.equal(true)\n })\n\n it('a toggle on the asking page wins while that page is open', () => {\n expect(railCollapsed({ stored: false, pageAsks: true, onPage: false })).to.equal(false)\n })\n\n it('a toggle on the page is ignored once the page no longer asks', () => {\n expect(railCollapsed({ stored: false, pageAsks: false, onPage: true })).to.equal(false)\n })\n\n it('only the declared value is a request', () => {\n expect(pageAsksRailCollapsed({ rail: 'collapsed' })).to.equal(true)\n expect(pageAsksRailCollapsed({ rail: 'open' })).to.equal(false)\n expect(pageAsksRailCollapsed({})).to.equal(false)\n expect(pageAsksRailCollapsed(undefined)).to.equal(false)\n expect(pageAsksRailCollapsed(null)).to.equal(false)\n })\n })\n\n describe('ox-menu-part', () => {\n const KEY = 'ox-menu.collapsed'\n\n function go(page: string, context: any) {\n store.dispatch({ type: UPDATE_PAGE, page, resourceId: '', params: {} } as any)\n store.dispatch({ type: UPDATE_CONTEXT, context } as any)\n }\n\n async function settle(el: OxMenuPart) {\n await el.updateComplete\n await nextFrame()\n await el.updateComplete\n }\n\n async function mount(stored: '0' | '1') {\n localStorage.setItem(KEY, stored)\n go('list-page', { title: 'list' })\n const el = (await fixture(html`<ox-menu-part rail></ox-menu-part>`)) as OxMenuPart\n await settle(el)\n return el\n }\n\n afterEach(() => {\n localStorage.removeItem(KEY)\n go('list-page', { title: 'list' })\n })\n\n it('folds on the asking page and opens again on leaving, without touching the stored choice', async () => {\n const el = await mount('0')\n expect(el.collapsed).to.equal(false)\n\n go('modeller-page', { title: 'modeller', rail: 'collapsed' })\n await settle(el)\n expect(el.collapsed).to.equal(true)\n expect(el.hasAttribute('collapsed')).to.equal(true)\n expect(localStorage.getItem(KEY)).to.equal('0')\n\n go('list-page', { title: 'list' })\n await settle(el)\n expect(el.collapsed).to.equal(false)\n })\n\n it('a rail the person folded stays folded after leaving the asking page', async () => {\n const el = await mount('1')\n\n go('modeller-page', { title: 'modeller', rail: 'collapsed' })\n await settle(el)\n go('list-page', { title: 'list' })\n await settle(el)\n\n expect(el.collapsed).to.equal(true)\n })\n\n it('opening it by hand on the asking page is not stored and the next visit folds again', async () => {\n const el = await mount('0')\n\n go('modeller-page', { title: 'modeller', rail: 'collapsed' })\n await settle(el)\n el.renderRoot.querySelector('ox-menu-portrait')!.dispatchEvent(\n new CustomEvent('collapse-toggle', { bubbles: true, composed: true })\n )\n await settle(el)\n expect(el.collapsed).to.equal(false)\n expect(localStorage.getItem(KEY)).to.equal('0')\n\n go('list-page', { title: 'list' })\n await settle(el)\n go('modeller-page', { title: 'modeller', rail: 'collapsed' })\n await settle(el)\n expect(el.collapsed).to.equal(true)\n })\n\n it('a menu without the rail does not fold for the request', async () => {\n localStorage.setItem(KEY, '0')\n const el = (await fixture(html`<ox-menu-part></ox-menu-part>`)) as OxMenuPart\n go('modeller-page', { title: 'modeller', rail: 'collapsed' })\n await settle(el)\n\n expect(el.hasAttribute('collapsed')).to.equal(false)\n })\n })\n})\n"]}
|