@operato/menu 10.7.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.
Files changed (42) hide show
  1. package/CHANGELOG.md +47 -0
  2. package/dist/src/index.d.ts +18 -0
  3. package/dist/src/index.js +4 -1
  4. package/dist/src/index.js.map +1 -1
  5. package/dist/src/menu-active.d.ts +89 -0
  6. package/dist/src/menu-active.js +115 -0
  7. package/dist/src/menu-active.js.map +1 -0
  8. package/dist/src/menu-mode.d.ts +98 -0
  9. package/dist/src/menu-mode.js +100 -0
  10. package/dist/src/menu-mode.js.map +1 -0
  11. package/dist/src/menu-portrait-styles.d.ts +27 -0
  12. package/dist/src/menu-portrait-styles.js +516 -92
  13. package/dist/src/menu-portrait-styles.js.map +1 -1
  14. package/dist/src/menu-privilege.d.ts +34 -0
  15. package/dist/src/menu-privilege.js +22 -0
  16. package/dist/src/menu-privilege.js.map +1 -0
  17. package/dist/src/ox-menu-part.d.ts +100 -3
  18. package/dist/src/ox-menu-part.js +220 -82
  19. package/dist/src/ox-menu-part.js.map +1 -1
  20. package/dist/src/ox-menu-portrait.d.ts +158 -3
  21. package/dist/src/ox-menu-portrait.js +511 -13
  22. package/dist/src/ox-menu-portrait.js.map +1 -1
  23. package/dist/src/types.d.ts +38 -1
  24. package/dist/src/types.js.map +1 -1
  25. package/dist/test/menu-active.test.d.ts +1 -0
  26. package/dist/test/menu-active.test.js +131 -0
  27. package/dist/test/menu-active.test.js.map +1 -0
  28. package/dist/test/menu-mode.test.d.ts +1 -0
  29. package/dist/test/menu-mode.test.js +108 -0
  30. package/dist/test/menu-mode.test.js.map +1 -0
  31. package/dist/test/menu-privilege.test.d.ts +1 -0
  32. package/dist/test/menu-privilege.test.js +94 -0
  33. package/dist/test/menu-privilege.test.js.map +1 -0
  34. package/dist/test/ox-menu-portrait.test.d.ts +1 -0
  35. package/dist/test/ox-menu-portrait.test.js +339 -0
  36. package/dist/test/ox-menu-portrait.test.js.map +1 -0
  37. package/dist/tsconfig.tsbuildinfo +1 -1
  38. package/package.json +8 -8
  39. package/test/menu-active.test.ts +162 -0
  40. package/test/menu-mode.test.ts +135 -0
  41. package/test/menu-privilege.test.ts +138 -0
  42. package/test/ox-menu-portrait.test.ts +431 -0
@@ -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'\n\n/** 메뉴가 선언한 경로. 쿼리는 떼고 본다. */\nfunction declaredPath(menu: Menu) {\n return menu.path?.split('?')[0]\n}\n\n/**\n * 메뉴가 **바로 그 자리**를 가리키는가.\n *\n * 주소가 `a/b` 이고 메뉴도 `a/b` 인 경우다.\n */\nfunction pointsAtSamePlace(menu: Menu, path: string) {\n return declaredPath(menu) === path\n}\n\n/**\n * 메뉴가 **같은 화면**을 가리키는가.\n *\n * 메뉴는 화면을 가리키고, 주소는 그 화면 안의 자리까지 담을 수 있다.\n *\n * /approval-pending-list/reference 결재함의 참조 묶음\n * /quality-list/disposition 품질 화면의 부적합 처분\n *\n * 라우터는 주소를 두 토막으로 자른다(`page` 와 `resourceId`). 그 둘을 이어 붙인 문자열을 메뉴 경로와\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 * 목록에서 줄을 눌러 들어가는 상세 화면은 **메뉴에 아예 없다** — `/activity-approval/<id>` 를\n * 가리키는 메뉴 항목은 없다. 그러면 왼쪽 메뉴에 아무 표시가 없고, 들어간 사람이 자기가 어디에 있는지\n * 잃는다. 돌아갈 곳도 눈에 안 보인다.\n *\n * 그래서 화면이 `context.menu` 에 자기가 딸린 메뉴의 경로를 적을 수 있게 한다. 어느 목록에서 열리는지는\n * **그 화면이 가장 잘 안다** — 메뉴 쪽에 상세 화면 목록을 나열해 두면 화면이 늘 때마다 그 목록을 같이\n * 고쳐야 하고, 빠뜨린 화면만 조용히 표시를 잃는다.\n */\nfunction pageSaysItBelongsHere(menu: Menu, routeContext?: { menu?: string }) {\n const belongsTo = routeContext?.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@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 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 @state() _activeTopLevel?: Menu\n @state() _activeMenu?: Menu\n @state() _path?: string\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 ></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 this._activeTopLevel = (e as CustomEvent).detail\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 * 최상위 자신이 맞으면 그것이 곧 그 자리의 메뉴다.\n */\n private lookup(menus: Menu[], matches: (menu: Menu) => boolean) {\n for (const top of menus) {\n if (matches(top)) {\n return { top, menu: top }\n }\n\n const inner = top.menus?.find(matches)\n if (inner) {\n return { top, menu: inner }\n }\n }\n\n return undefined\n }\n\n private findActivePage() {\n const { page, resourceId, context } = this._route\n const path = resourceId ? `${page}/${resourceId}` : page\n const menus = this._menu.menus || []\n\n /*\n * 여러 번 훑는 이유 — 순서가 뜻을 갖는다.\n *\n * `a` 와 `a/b` 두 메뉴가 함께 있고 주소가 `a/b` 이면 **`a/b` 가 이겨야 한다.** 한 번에 훑으면서\n * 조건을 OR 로 묶으면 목록에서 먼저 나온 `a` 가 이기고, 실제로 보고 있는 자리를 가리키는 메뉴가\n * 표시를 못 받는다.\n *\n * 분명한 것부터 본다 — 바로 그 자리 · 화면이 말한 것 · 같은 화면 · 앱이 준 판정.\n */\n const found =\n this.lookup(menus, menu => pointsAtSamePlace(menu, path)) ||\n this.lookup(menus, menu => pageSaysItBelongsHere(menu, context)) ||\n this.lookup(menus, menu => pointsAtSamePage(menu, page) || saysItIsActive(menu, path, page))\n\n this._path = path\n this._activeTopLevel = found?.top\n this._activeMenu = found?.menu || found?.top\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 { 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,13 +1,168 @@
1
1
  import '@material/web/icon/icon.js';
2
2
  import { LitElement, nothing, TemplateResult } from 'lit';
3
3
  import { Menu } from './types.js';
4
- export declare class OxMenuPortrait extends LitElement {
4
+ declare const OxMenuPortrait_base: (new (...args: any[]) => LitElement) & typeof LitElement;
5
+ export declare class OxMenuPortrait extends OxMenuPortrait_base {
5
6
  static styles: import("lit").CSSResult[];
6
7
  menus?: Menu[];
7
8
  activeTopLevel?: Menu;
8
9
  activeMenu?: Menu;
9
10
  path: string;
10
- renderMenus(menus: Menu[], activeTopLevel?: Menu, activeMenu?: Menu): TemplateResult | typeof nothing;
11
- render(): TemplateResult | typeof nothing;
11
+ /**
12
+ * 레일 모드 접을 수 있고, 접히면 하위가 팝업으로 뜬다.
13
+ *
14
+ * **옵트인이다.** 켜지 않은 앱은 지금과 한 글자도 다르지 않다. 모양이 함께 바뀌는 것은 취향이
15
+ * 아니라 요건이다 — 기존 모양은 줄마다 폭 전체를 쓰는 밑줄이 있고 왼쪽 여백이 16px 이라 56px
16
+ * 에서 무너진다(§`menu-rail-styles.ts`).
17
+ */
18
+ rail: boolean;
19
+ /**
20
+ * 접힘 — 아이콘만.
21
+ *
22
+ * **주인은 이 컴포넌트가 아니다.** 폭을 정하는 요소는 `ox-menu-part` 이고(그쪽에 `min-width`
23
+ * 가 있다), 셸의 리사이즈 손잡이도 그 요소에 인라인 폭을 쓴다. 그래서 상태와 저장은 그쪽이
24
+ * 갖고 여기는 받아서 그린다.
25
+ */
26
+ collapsed: boolean;
27
+ /**
28
+ * 서랍인가 — 화면 위에 겹쳐 떴다가 닫히는 메뉴.
29
+ *
30
+ * 그때 최상위는 **펼치기만** 한다. 누르는 순간 서랍이 닫히면 그 묶음의 나머지 하위를 볼
31
+ * 기회가 없다(§`ox-menu-part` 의 `drawer`).
32
+ */
33
+ drawer: boolean;
34
+ /**
35
+ * 지금 어떤 모양으로 서 있나 — 세 플래그에서 하나로 모은다.
36
+ *
37
+ * 이 값과 그에 딸린 두 답(`하위를 어디에` · `최상위가 이동하나`)은 `menu-mode.ts` 에 있고
38
+ * 시험으로 못박혀 있다. 여기서 그때그때 판단하지 않는다 — 그렇게 하다가 네 번 깨졌다.
39
+ */
40
+ private get mode();
41
+ /** 접힌 상태에서 팝업으로 열린 최상위 메뉴. 시스템 팝업이면 SYSTEM 이다. */
42
+ private popped?;
43
+ /**
44
+ * 시스템 항목 — 프로필 · 로그아웃 · 설정처럼 **앱이 아니라 프레임워크가 붙이는 것**들.
45
+ *
46
+ * 하드코딩하지 않는다. 각 패키지가 부팅할 때 `ADD_MORENDA` 로 등록하고(권한이 없으면 등록
47
+ * 자체를 안 한다), 우리는 그 목록을 그대로 그린다. 그래서 목록이 비면 이 자리도 없다.
48
+ */
49
+ morendas: any[];
50
+ private _unsubscribeStore?;
51
+ /** 팝업 자리 — 누른 항목의 오른쪽. 세로는 그린 뒤 재서 맞춘다(§`fitPopup`). */
52
+ private _popLeft;
53
+ private _popTop;
54
+ /** 누른 항목의 윗선. 화면에 들어가면 이 자리를 쓴다. */
55
+ private _popPrefer;
56
+ /** 팝업이 화면 안에 들어가게 세로 자리를 맞춘다. 팝업 밖 여백. */
57
+ private static readonly POP_MARGIN;
58
+ /**
59
+ * 팝업 밖을 누르면 닫는다.
60
+ *
61
+ * ── 팝업 안의 클릭을 막지 않는다 ────────────────────────────────────────
62
+ * 한때 팝업 컨테이너에 stopPropagation 을 걸어 두었다. 팝업 안을 눌러도 이 처리기가 받아
63
+ * 곧바로 닫히던 것을 막으려던 것인데, 그것이 **셸의 라우터까지 막았다.**
64
+ *
65
+ * 라우터는 document.body 에서 클릭을 받아 composedPath 로 A 를 찾고 preventDefault 한 뒤
66
+ * 화면만 바꾼다(@operato/shell 의 installRouter). 클릭이 거기까지 못 가면 브라우저의 기본
67
+ * 이동이 그대로 일어나 **페이지 전체가 다시 읽혔다.**
68
+ *
69
+ * 그래서 막지 않고, 여기서 「팝업 안이면 닫지 않는다」로 가른다. 줄을 누를 때 닫는 일은 그
70
+ * 줄의 처리기가 한다.
71
+ */
72
+ private _onDocClick;
73
+ connectedCallback(): void;
74
+ disconnectedCallback(): void;
75
+ /**
76
+ * 마우스를 올렸을 때 뜨는 글.
77
+ *
78
+ * ── 접혔을 때는 이름이 먼저다 ───────────────────────────────────────────
79
+ * 접히면 아이콘만 남으므로 **이름을 모른다.** 그때는 이름을 윗줄에 얹고 설명을 아랫줄에 둔다.
80
+ * 펼쳐져 있으면 이름은 이미 옆에 있으니 설명만 낸다.
81
+ *
82
+ * 줄바꿈으로 잇는다 — 대시로 이으면 설명 안의 대시와 겹쳐 「A — B — C」가 된다.
83
+ *
84
+ * 설명도 없고 이름도 안 보이는 경우가 아니면 아무것도 내지 않는다. 이름을 그대로 되풀이하는
85
+ * 툴팁은 소음이다.
86
+ */
87
+ private hoverText;
88
+ /**
89
+ * @param depth 0 이면 최상위다. **하위도 `active` 를 달기 때문에** 깊이를 값으로 넘긴다 —
90
+ * CSS 로 「최상위인 li」를 가리려면 `:not(li li)` 같은 것이 필요하고, 그건 읽는 사람이
91
+ * 한 번 더 생각해야 하는 선택자다.
92
+ */
93
+ renderMenus(menus: Menu[], activeTopLevel?: Menu, activeMenu?: Menu, depth?: number): TemplateResult | typeof nothing;
94
+ render(): TemplateResult<1>;
95
+ /**
96
+ * 시스템 항목 — **바닥에 붙는다.**
97
+ *
98
+ * 프로필 · 로그아웃 · 설정은 하루에 한 번 여는 것이고, 일하는 화면들과 같은 깊이에 있으면
99
+ * 자주 여는 것을 밀어낸다. 그래서 메뉴 끝에 두고 사이를 빈 자리로 벌린다.
100
+ *
101
+ * 목록이 비면 이 자리도 없다 — 각 패키지가 권한이 없으면 등록 자체를 안 하므로, 비어 있는
102
+ * 것은 「볼 것이 없다」는 뜻이다. 빈 묶음 이름만 남겨 두면 눌러도 아무 일이 없다.
103
+ *
104
+ * 펼친 상태에서도 팝업으로 연다. 이 항목들은 **주소가 아니라 함수**라, 그 자리에서 펼치면
105
+ * 옆의 하위 메뉴들과 같은 모양이 되어 「눌러도 화면이 안 바뀌는 메뉴」로 읽힌다.
106
+ */
107
+ private renderSystem;
108
+ /** 시스템 항목은 주소가 아니라 함수다. 이동을 막고 부른다. */
109
+ private runMorenda;
110
+ /**
111
+ * 시스템 줄을 누른다 — **다른 최상위와 똑같은 두 갈래다.**
112
+ *
113
+ * 한때 시스템만 자기 상태(`popped`)를 따로 썼다. 다른 최상위는 `activeTopLevel` 하나를 나눠
114
+ * 쓰는데 시스템만 그 밖에 있었으니, **다른 묶음이 열려 있어도 시스템이 함께 열렸다.** 한 번에
115
+ * 하나만 열리는 규칙에서 이 줄만 빠져 있었다.
116
+ *
117
+ * 이제 같은 상태를 쓴다. 접힌 레일에서만 팝업이고, 그때는 다른 최상위도 팝업이다.
118
+ */
119
+ private openSystem;
120
+ /**
121
+ * 접힌 상태에서 하위 메뉴를 팝업으로 낸다.
122
+ *
123
+ * 그 자리에서 열지 않는 이유는 56px 안에 들여쓴 목록이 들어가지 않기 때문이다. 접힌 레일에서
124
+ * 하위를 열 길이 없으면 **접는 순간 화면의 절반에 갈 수 없게 된다** — 접기가 기능이 아니라
125
+ * 함정이 된다.
126
+ */
127
+ private renderPopped;
128
+ /**
129
+ * 시스템 항목 줄들 — **이것만 따로 그린다.**
130
+ *
131
+ * morenda 는 주소가 아니라 함수를 갖는다(`action`). 아이콘과 이름도 Lit 템플릿이라 `Menu` 모양이
132
+ * 아니다. 억지로 `Menu` 로 바꿔 넘기면 그 변환이 또 옮겨 적는 자리가 된다.
133
+ */
134
+ private renderSystemRows;
135
+ /**
136
+ * 팝업이 화면 안에 들어가게 세로 자리를 맞춘다.
137
+ *
138
+ * ── 왜 그린 뒤에 하나 ───────────────────────────────────────────────────
139
+ * 높이를 알아야 맞출 수 있고, 높이는 그려 봐야 안다. 항목마다 하위 수가 다르다 — 「기준정보」는
140
+ * 열여섯 줄이고 「배우기」는 한 줄이다.
141
+ *
142
+ * ── 규칙 ────────────────────────────────────────────────────────────────
143
+ * 누른 항목의 윗선에서 시작하는 것이 바람이다. 그대로 두면 아래쪽 항목에서 팝업이 화면 밖으로
144
+ * 나간다. 그래서 **아래로 넘치는 만큼 위로 올린다.** 그래도 안 들어가면 위 여백에 붙이고
145
+ * 그때만 스크롤한다.
146
+ *
147
+ * 자리를 반응 상태로 두지 않고 style 에 바로 쓴다 — 값이 바뀔 때마다 다시 그리면 그 렌더에서
148
+ * 또 재게 되고, 재는 값이 그리기에 달려 있으므로 서로 물린다.
149
+ */
150
+ private fitPopup;
151
+ /**
152
+ * 노브를 그 항목의 가운데 높이에 둔다.
153
+ *
154
+ * ── 범위를 벗어나면 감추지 않고 끝에 붙인다 ──────────────────────────────
155
+ * 팝업이 위로 밀리는 것은 **아래에 자리가 없어서**이고, 그만큼만 밀린다. 그래서 그 항목은
156
+ * 거의 항상 팝업의 세로 범위 안으로 들어온다. 벗어나는 것은 라운드 모서리에 걸치는 몇 px
157
+ * 뿐이다. 그때 감추면 **자리가 좁을 때만 표시가 사라진다** — 가장 필요한 순간이다.
158
+ *
159
+ * 그래서 모서리 안쪽으로 붙인다. 정확히 그 줄을 가리키지 못해도 「위쪽」·「아래쪽」은
160
+ * 가리키고, 어느 항목인지는 그 항목의 popped 표시가 말한다.
161
+ */
162
+ private fitKnob;
163
+ protected updated(): void;
164
+ /** 접기/펼치기는 `ox-menu-part` 가 한다 — 폭과 저장이 그쪽 것이다. 여기서는 뜻만 올린다. */
165
+ private toggleCollapsed;
12
166
  firstUpdated(): void;
13
167
  }
168
+ export {};