@operato/layout 10.29.68 → 10.29.70

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -3,6 +3,35 @@
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.29.70](https://github.com/hatiolab/operato/compare/v10.29.69...v10.29.70) (2026-11-07)
7
+
8
+ **Note:** Version bump only for package @operato/layout
9
+
10
+
11
+
12
+
13
+
14
+ ### [10.29.69](https://github.com/hatiolab/operato/compare/v10.29.68...v10.29.69) (2026-11-07)
15
+
16
+
17
+ ### :bug: Bug Fix
18
+
19
+ * **layout:** a 360 section keeps drawing what it holds while it reads again ([2952bee](https://github.com/hatiolab/operato/commit/2952bee8eef13b2a426822dbe1e7f1f92b5683d2))
20
+ * 모바일화면에서 페이지 왼쪽 보더라인 제거 ([2b6f338](https://github.com/hatiolab/operato/commit/2b6f3381217e612c3f4e44d5d843c6e4a53902fb))
21
+
22
+
23
+ ### :house: Code Refactoring
24
+
25
+ * **layout:** remove Entity360Reads - sections declare their queries ([306b637](https://github.com/hatiolab/operato/commit/306b637dec863cecf0ffdf21af6911df5deeb8b5))
26
+
27
+
28
+ ### :rocket: New Features
29
+
30
+ * **layout:** a 360 section reads through a declared query - entity360Answer and dock refresh ([1a99806](https://github.com/hatiolab/operato/commit/1a998068e4861fbd2e7a2f061958b7f6cdfcba66))
31
+ * **shell:** a draft keeps its edits in this browser - DraftController takes over keep/restore ([2f608f1](https://github.com/hatiolab/operato/commit/2f608f14d66dfe7a60968aae403adb70626cd841))
32
+
33
+
34
+
6
35
  ### [10.29.68](https://github.com/hatiolab/operato/compare/v10.29.67...v10.29.68) (2026-11-06)
7
36
 
8
37
  **Note:** Version bump only for package @operato/layout
@@ -26,6 +26,15 @@ export declare function readEntity360Door<T>(response: {
26
26
  data?: any;
27
27
  errors?: readonly any[] | null;
28
28
  } | null | undefined, pick: (data: any) => T, isEmpty?: (value: T) => boolean): Entity360DoorAnswer<T>;
29
+ /**
30
+ * A section's answer from the query it declared (ADR-0190 `QueryController`): `undefined` while it first reads, then
31
+ * one of the four states - kept while it reads again. A refusal stays `forbidden`; a request that threw is `failed` with its own message.
32
+ */
33
+ export declare function entity360Answer<T>(query: {
34
+ loading: boolean;
35
+ data?: any;
36
+ error?: unknown;
37
+ }, pick: (data: any) => T, isEmpty?: (value: T) => boolean): Entity360DoorAnswer<T> | undefined;
29
38
  /**
30
39
  * Ask a door and read its answer - `readEntity360Door` for a request still on its way (ADR-0067 decision 7).
31
40
  *
@@ -36,21 +45,3 @@ export declare function askEntity360Door<T>(request: Promise<{
36
45
  data?: any;
37
46
  errors?: readonly any[] | null;
38
47
  } | null | undefined>, pick: (data: any) => T, isEmpty?: (value: T) => boolean): Promise<Entity360DoorAnswer<T>>;
39
- /**
40
- * One section's reads, of which only the newest may answer (ADR-0067 decision 7).
41
- *
42
- * A section reads again whenever the dock hands it a subject - another subject, or the same one after a refresh. An
43
- * answer asked for before that is old: drawn, it would show the previous subject, or what stood before the refresh,
44
- * under the new heading. Comparing ids does not catch the refresh (the id is the same), so each read takes a number
45
- * and only the last one taken is let through. A request that throws comes back `failed` (`askEntity360Door`).
46
- */
47
- export declare class Entity360Reads {
48
- private latest;
49
- /** The answer, or `undefined` when a newer read began (or `forget` was called) while this one was on its way. */
50
- ask<T>(request: () => Promise<{
51
- data?: any;
52
- errors?: readonly any[] | null;
53
- } | null | undefined>, pick: (data: any) => T, isEmpty?: (value: T) => boolean): Promise<Entity360DoorAnswer<T> | undefined>;
54
- /** There is nothing to ask: an answer still on its way is no longer wanted. */
55
- forget(): void;
56
- }
@@ -25,6 +25,23 @@ export function readEntity360Door(response, pick, isEmpty = value => value == nu
25
25
  const value = pick(response.data);
26
26
  return isEmpty(value) ? { state: 'none', data: value } : { state: 'ready', data: value };
27
27
  }
28
+ /**
29
+ * A section's answer from the query it declared (ADR-0190 `QueryController`): `undefined` while it first reads, then
30
+ * one of the four states - kept while it reads again. A refusal stays `forbidden`; a request that threw is `failed` with its own message.
31
+ */
32
+ export function entity360Answer(query, pick, isEmpty) {
33
+ var _a, _b, _c;
34
+ /* A re-read (a refresh, a save) keeps drawing what the section holds: only a first read is "loading". */
35
+ if (query.data === undefined && (query.loading || query.error === undefined))
36
+ return undefined;
37
+ if (query.error) {
38
+ const errors = (_a = query.error) === null || _a === void 0 ? void 0 : _a.graphQLErrors;
39
+ if (errors === null || errors === void 0 ? void 0 : errors.length)
40
+ return readEntity360Door({ errors }, pick, isEmpty);
41
+ return { state: 'failed', message: String((_c = (_b = query.error) === null || _b === void 0 ? void 0 : _b.message) !== null && _c !== void 0 ? _c : '') };
42
+ }
43
+ return readEntity360Door({ data: query.data }, pick, isEmpty);
44
+ }
28
45
  /**
29
46
  * Ask a door and read its answer - `readEntity360Door` for a request still on its way (ADR-0067 decision 7).
30
47
  *
@@ -46,28 +63,4 @@ export async function askEntity360Door(request, pick, isEmpty) {
46
63
  }
47
64
  return readEntity360Door(response, pick, isEmpty);
48
65
  }
49
- /**
50
- * One section's reads, of which only the newest may answer (ADR-0067 decision 7).
51
- *
52
- * A section reads again whenever the dock hands it a subject - another subject, or the same one after a refresh. An
53
- * answer asked for before that is old: drawn, it would show the previous subject, or what stood before the refresh,
54
- * under the new heading. Comparing ids does not catch the refresh (the id is the same), so each read takes a number
55
- * and only the last one taken is let through. A request that throws comes back `failed` (`askEntity360Door`).
56
- */
57
- export class Entity360Reads {
58
- constructor() {
59
- this.latest = 0;
60
- }
61
- /** The answer, or `undefined` when a newer read began (or `forget` was called) while this one was on its way. */
62
- async ask(request, pick, isEmpty) {
63
- const mine = ++this.latest;
64
- /* A request that throws before it returns a promise is a rejected one too. */
65
- const answer = await askEntity360Door((async () => request())(), pick, isEmpty);
66
- return mine === this.latest ? answer : undefined;
67
- }
68
- /** There is nothing to ask: an answer still on its way is no longer wanted. */
69
- forget() {
70
- this.latest++;
71
- }
72
- }
73
66
  //# sourceMappingURL=entity360-door.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"entity360-door.js","sourceRoot":"","sources":["../../../src/actions/entity360-door.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * What a 360 section's read door answered - pure: a GraphQL response in, a state out.\n *\n * A section shows one of four things once it has asked (ADR-0067's rule for the 360):\n * - `ready` - there is something, and the section draws it\n * - `none` - the door answered and there is nothing: 「없음」\n * - `forbidden` - the door refused this viewer: 「볼 수 없음」. Not the same as `none` - saying\n * \"none\" to someone who may not look would state a fact the screen does not know.\n * - `failed` - something else went wrong; the server's own sentence is kept, since the section\n * does not know the cause and must not make one up.\n *\n * A refusal is told by `extensions.code == 'FORBIDDEN'`, which is what the @privilege directive sends.\n */\n\nexport type Entity360DoorState = 'loading' | 'ready' | 'none' | 'forbidden' | 'failed'\n\nexport interface Entity360DoorAnswer<T> {\n state: Exclude<Entity360DoorState, 'loading'>\n data?: T\n /** The server's sentence, for `failed`. */\n message?: string\n}\n\n/**\n * Read a response. `pick` takes what the section draws out of `data`; `isEmpty` says whether that is\n * nothing (by default: `null`, `undefined` or an empty array).\n */\nexport function readEntity360Door<T>(\n response: { data?: any; errors?: readonly any[] | null } | null | undefined,\n pick: (data: any) => T,\n isEmpty: (value: T) => boolean = value => value == null || (Array.isArray(value) && value.length === 0)\n): Entity360DoorAnswer<T> {\n const errors = response?.errors || []\n\n if (errors.some((error: any) => error?.extensions?.code == 'FORBIDDEN')) return { state: 'forbidden' }\n if (errors.length > 0 || !response?.data) {\n return { state: 'failed', message: errors.map((error: any) => error?.message).filter(Boolean).join(' ') }\n }\n\n const value = pick(response.data)\n return isEmpty(value) ? { state: 'none', data: value } : { state: 'ready', data: value }\n}\n\n/**\n * Ask a door and read its answer - `readEntity360Door` for a request still on its way (ADR-0067 decision 7).\n *\n * A request that throws (the network is down, the server is gone) is `failed` with its own message, so a section\n * always comes to rest in one of the four states; nothing is left saying 「loading」 for good.\n */\nexport async function askEntity360Door<T>(\n request: Promise<{ data?: any; errors?: readonly any[] | null } | null | undefined>,\n pick: (data: any) => T,\n isEmpty?: (value: T) => boolean\n): Promise<Entity360DoorAnswer<T>> {\n let response\n try {\n response = await request\n } catch (error) {\n /* A client set to throw on GraphQL errors still carries them: a refusal stays `forbidden`. */\n const errors = (error as any)?.graphQLErrors\n if (errors?.length) return readEntity360Door({ errors }, pick, isEmpty)\n return { state: 'failed', message: String((error as any)?.message ?? '') }\n }\n return readEntity360Door(response, pick, isEmpty)\n}\n\n/**\n * One section's reads, of which only the newest may answer (ADR-0067 decision 7).\n *\n * A section reads again whenever the dock hands it a subject - another subject, or the same one after a refresh. An\n * answer asked for before that is old: drawn, it would show the previous subject, or what stood before the refresh,\n * under the new heading. Comparing ids does not catch the refresh (the id is the same), so each read takes a number\n * and only the last one taken is let through. A request that throws comes back `failed` (`askEntity360Door`).\n */\nexport class Entity360Reads {\n private latest = 0\n\n /** The answer, or `undefined` when a newer read began (or `forget` was called) while this one was on its way. */\n async ask<T>(\n request: () => Promise<{ data?: any; errors?: readonly any[] | null } | null | undefined>,\n pick: (data: any) => T,\n isEmpty?: (value: T) => boolean\n ): Promise<Entity360DoorAnswer<T> | undefined> {\n const mine = ++this.latest\n /* A request that throws before it returns a promise is a rejected one too. */\n const answer = await askEntity360Door((async () => request())(), pick, isEmpty)\n return mine === this.latest ? answer : undefined\n }\n\n /** There is nothing to ask: an answer still on its way is no longer wanted. */\n forget(): void {\n this.latest++\n }\n}\n"]}
1
+ {"version":3,"file":"entity360-door.js","sourceRoot":"","sources":["../../../src/actions/entity360-door.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * What a 360 section's read door answered - pure: a GraphQL response in, a state out.\n *\n * A section shows one of four things once it has asked (ADR-0067's rule for the 360):\n * - `ready` - there is something, and the section draws it\n * - `none` - the door answered and there is nothing: 「없음」\n * - `forbidden` - the door refused this viewer: 「볼 수 없음」. Not the same as `none` - saying\n * \"none\" to someone who may not look would state a fact the screen does not know.\n * - `failed` - something else went wrong; the server's own sentence is kept, since the section\n * does not know the cause and must not make one up.\n *\n * A refusal is told by `extensions.code == 'FORBIDDEN'`, which is what the @privilege directive sends.\n */\n\nexport type Entity360DoorState = 'loading' | 'ready' | 'none' | 'forbidden' | 'failed'\n\nexport interface Entity360DoorAnswer<T> {\n state: Exclude<Entity360DoorState, 'loading'>\n data?: T\n /** The server's sentence, for `failed`. */\n message?: string\n}\n\n/**\n * Read a response. `pick` takes what the section draws out of `data`; `isEmpty` says whether that is\n * nothing (by default: `null`, `undefined` or an empty array).\n */\nexport function readEntity360Door<T>(\n response: { data?: any; errors?: readonly any[] | null } | null | undefined,\n pick: (data: any) => T,\n isEmpty: (value: T) => boolean = value => value == null || (Array.isArray(value) && value.length === 0)\n): Entity360DoorAnswer<T> {\n const errors = response?.errors || []\n\n if (errors.some((error: any) => error?.extensions?.code == 'FORBIDDEN')) return { state: 'forbidden' }\n if (errors.length > 0 || !response?.data) {\n return { state: 'failed', message: errors.map((error: any) => error?.message).filter(Boolean).join(' ') }\n }\n\n const value = pick(response.data)\n return isEmpty(value) ? { state: 'none', data: value } : { state: 'ready', data: value }\n}\n\n/**\n * A section's answer from the query it declared (ADR-0190 `QueryController`): `undefined` while it first reads, then\n * one of the four states - kept while it reads again. A refusal stays `forbidden`; a request that threw is `failed` with its own message.\n */\nexport function entity360Answer<T>(\n query: { loading: boolean; data?: any; error?: unknown },\n pick: (data: any) => T,\n isEmpty?: (value: T) => boolean\n): Entity360DoorAnswer<T> | undefined {\n /* A re-read (a refresh, a save) keeps drawing what the section holds: only a first read is \"loading\". */\n if (query.data === undefined && (query.loading || query.error === undefined)) return undefined\n if (query.error) {\n const errors = (query.error as any)?.graphQLErrors\n if (errors?.length) return readEntity360Door({ errors }, pick, isEmpty)\n return { state: 'failed', message: String((query.error as any)?.message ?? '') }\n }\n return readEntity360Door({ data: query.data }, pick, isEmpty)\n}\n\n/**\n * Ask a door and read its answer - `readEntity360Door` for a request still on its way (ADR-0067 decision 7).\n *\n * A request that throws (the network is down, the server is gone) is `failed` with its own message, so a section\n * always comes to rest in one of the four states; nothing is left saying 「loading」 for good.\n */\nexport async function askEntity360Door<T>(\n request: Promise<{ data?: any; errors?: readonly any[] | null } | null | undefined>,\n pick: (data: any) => T,\n isEmpty?: (value: T) => boolean\n): Promise<Entity360DoorAnswer<T>> {\n let response\n try {\n response = await request\n } catch (error) {\n /* A client set to throw on GraphQL errors still carries them: a refusal stays `forbidden`. */\n const errors = (error as any)?.graphQLErrors\n if (errors?.length) return readEntity360Door({ errors }, pick, isEmpty)\n return { state: 'failed', message: String((error as any)?.message ?? '') }\n }\n return readEntity360Door(response, pick, isEmpty)\n}\n"]}
@@ -3,7 +3,7 @@ import { i18next } from '@operato/i18n';
3
3
  import { OxPrompt } from '@operato/popup/ox-prompt.js';
4
4
  import { addressController, writeAddress } from '@operato/utils/address.js';
5
5
  import { store, UPDATE_CONTEXT } from '@operato/shell';
6
- import { showEntity360, hideEntity360, canGoBackEntity360, isEntity360Shown, isEntity360Unkept, navigateEntity360 } from './entity360.js';
6
+ import { showEntity360, hideEntity360, canGoBackEntity360, isEntity360Shown, isEntity360Unkept, navigateEntity360, refetchEntity360 } from './entity360.js';
7
7
  const returnFocusByAddress = new Map();
8
8
  const kinds = new Map();
9
9
  const kindOfTypename = new Map();
@@ -190,6 +190,7 @@ export function refreshEntity360(about) {
190
190
  if (!shown || !isEntity360Shown(OWNER) || !about({ kind: shown.kind, id: shown.id }))
191
191
  return false;
192
192
  draw({ ...shown });
193
+ refetchEntity360(OWNER);
193
194
  return true;
194
195
  }
195
196
  const listeners = new Set();
@@ -241,8 +242,10 @@ function draw(subject, returnFocus) {
241
242
  onBack: canGoBackEntity360() ? () => history.back() : undefined,
242
243
  /* A new subject of the same kind and id: every section reads its door again and keeps what it holds. */
243
244
  onRefresh: () => {
244
- if (shown === subject)
245
- draw({ ...subject });
245
+ if (shown !== subject)
246
+ return;
247
+ draw({ ...subject });
248
+ refetchEntity360(OWNER);
246
249
  }
247
250
  }, returnFocus);
248
251
  if (declaration === null || declaration === void 0 ? void 0 : declaration.assistantSubject)
@@ -1 +1 @@
1
- {"version":3,"file":"entity360-registry.js","sourceRoot":"","sources":["../../../src/actions/entity360-registry.ts"],"names":[],"mappings":"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","sourcesContent":["import { html, type TemplateResult } from 'lit'\nimport { i18next } from '@operato/i18n'\nimport { OxPrompt } from '@operato/popup/ox-prompt.js'\nimport { addressController, writeAddress } from '@operato/utils/address.js'\n\nimport { store, UPDATE_CONTEXT } from '@operato/shell'\n\nimport { showEntity360, hideEntity360, canGoBackEntity360, isEntity360Shown, isEntity360Unkept, navigateEntity360 } from './entity360.js'\n\nconst returnFocusByAddress = new Map<string, () => void>()\n\n/**\n * Which kinds of thing open a 360, and what each module puts in it (ADR-0067, the person 360 ruling).\n *\n * An entity's name is a link to its 360 (ADR-0067 decision 1), and a grid shows names by the dozen -\n * `updater` alone is a person column on 65 framework screens. Writing an opener into every column\n * would never finish. Instead a kind is declared once with the GraphQL `__typename`s it claims, and\n * whatever draws a value can ask `entity360KindOf(value)`: registered, it is a link; not, it stays\n * text, exactly as before.\n *\n * This file knows no product words and imports no product module. A kind brings its own title; each\n * module hangs its own section with its own read door (`registerEntity360Section`), the way activity\n * panels hang on the activity detail. Opening a kind with no section says so in one line - never a\n * blank dock.\n */\n\n/** The thing a 360 is about: its kind, its id, and the value it was opened from. */\nexport interface Entity360Subject {\n kind: string\n id: string\n value: Record<string, any>\n}\n\nexport interface Entity360KindDeclaration {\n /** The name sections register under - 'person', 'role', … */\n kind: string\n /** The GraphQL `__typename`s whose values open this kind. A typename belongs to one kind. */\n typenames: string[]\n /**\n * The kind's one identifier for a value - what the address, the links and `subject.id` all carry (ADR-0067\n * decision 6). A section reads its door by `subject.id` alone: a reload or a shared link brings nothing else.\n * Without it, `value.id`.\n */\n id?(value: Record<string, any>): string | null | undefined\n /** The dock's title for a subject - what a person recognises it by. */\n title(subject: Entity360Subject): string\n /** A line under the title, if the kind has one. */\n context?(subject: Entity360Subject): string\n /**\n * The open 360 is what the page talks about: while it is open the page context's `subject` is\n * `{ type: kind, id }`, so the AI dock follows it (ai-assistant registerSubjectSurface). Closing\n * gives the page its own subject back. Off by default - a kind the assistant has no surface for\n * would only take the page's subject away.\n */\n assistantSubject?: boolean\n}\n\nexport interface Entity360Section {\n /** Unique within its kind. */\n name: string\n /** Lower comes first; equal orders keep the order they were registered in. */\n order?: number\n /** The section's content - usually the module's own element, which reads its own door. */\n render(subject: Entity360Subject): TemplateResult\n}\n\nexport type Entity360Rejection = { kind: string; name?: string; reason: string }\n\nconst kinds = new Map<string, Entity360KindDeclaration>()\nconst kindOfTypename = new Map<string, string>()\nconst sections = new Map<string, (Entity360Section & { at: number })[]>()\nconst rejected: Entity360Rejection[] = []\nlet registered = 0\n\n/** The dock has one owner for everything opened through this registry. */\nconst OWNER = {}\n\n/**\n * Declare a kind. A kind named twice, or a typename another kind already claims, is refused and\n * kept in `entity360Rejections()` - silently taking the second would reroute links nobody asked to move.\n */\nexport function registerEntity360Kind(declaration: Entity360KindDeclaration): void {\n const kind = String(declaration?.kind ?? '').trim()\n\n if (!kind) return void rejected.push({ kind: '(no kind)', reason: 'no-kind' })\n if (kinds.has(kind)) return void rejected.push({ kind, reason: 'duplicate-kind' })\n if (typeof declaration.title !== 'function') return void rejected.push({ kind, reason: 'no-title' })\n\n const taken = (declaration.typenames || []).find(typename => kindOfTypename.has(typename))\n if (taken) return void rejected.push({ kind, name: taken, reason: 'typename-claimed' })\n\n kinds.set(kind, { ...declaration, kind })\n for (const typename of declaration.typenames || []) kindOfTypename.set(typename, kind)\n}\n\n/** Hang a section on a kind. A name already on that kind is refused. */\nexport function registerEntity360Section(kind: string, section: Entity360Section): void {\n const name = String(section?.name ?? '').trim()\n\n if (!name) return void rejected.push({ kind, reason: 'no-name' })\n if (typeof section.render !== 'function') return void rejected.push({ kind, name, reason: 'no-render' })\n\n const list = sections.get(kind) ?? []\n if (list.some(one => one.name === name)) return void rejected.push({ kind, name, reason: 'duplicate-name' })\n\n list.push({ ...section, name, at: registered++ })\n list.sort((a, b) => (a.order ?? 0) - (b.order ?? 0) || a.at - b.at)\n sections.set(kind, list)\n}\n\n/**\n * The kind a value opens, or `undefined`.\n *\n * `undefined` for anything not registered and for a value without `__typename` (a call that turned\n * the cache off) - the caller draws it as it always has.\n */\nexport function entity360KindOf(value: unknown): string | undefined {\n const typename = (value as any)?.__typename\n return typeof typename === 'string' ? kindOfTypename.get(typename) : undefined\n}\n\n/** The identifier a kind gives a value (its declaration's `id`, else `value.id`); '' when there is none. */\nexport function entity360IdOf(kind: string, value: Record<string, any> | null | undefined): string {\n const declared = kinds.get(kind)?.id\n return String((declared ? declared(value ?? {}) : value?.id) ?? '')\n}\n\n/** Whether a kind was declared - an identifier of an undeclared kind stays text. */\nexport function entity360KindRegistered(kind: string): boolean {\n return kinds.has(kind)\n}\n\n/** The sections hung on a kind, in order. */\nexport function entity360Sections(kind: string): Entity360Section[] {\n return [...(sections.get(kind) ?? [])]\n}\n\n/** What was refused, and why - for a test or a boot check to read. */\nexport function entity360Rejections(): Entity360Rejection[] {\n return [...rejected]\n}\n\nlet shown: Entity360Subject | undefined\n/** Values opened in this page, by address - going back to one gives its section the value it was opened with. */\nconst opened = new Map<string, Record<string, any>>()\n/** The page's own subject while a 360 stands in for it, and the page it belongs to. */\nlet pageSubject: { path: string; subject: unknown; ours?: unknown } | undefined\n\n/** The address of a subject: `f=kind:id`. The id may hold colons (an EPC URN); the kind does not. */\nconst FOCUS = 'f'\nconst addressOf = (kind: string, id: string) => `${kind}:${id}`\n\nfunction subjectInAddress(): { kind: string; id: string } | undefined {\n const value = new URLSearchParams(location.search).get(FOCUS)\n const at = value ? value.indexOf(':') : -1\n return at > 0 ? { kind: value!.slice(0, at), id: value!.slice(at + 1) } : undefined\n}\n\n/**\n * Open the 360 of a value.\n *\n * A kind with sections shows them in order. A kind with none - or a value that opens no kind -\n * shows one line saying there is no 360 for it yet. The dock is never blank.\n */\nexport function openEntity360For(value: Record<string, any>, returnFocus?: () => void, source?: Element): void {\n openEntity360Kind(entity360KindOf(value) ?? '', value, returnFocus, source)\n}\n\n/**\n * Open the 360 of a kind directly - for a value known by its kind and id rather than by a GraphQL\n * typename: an identifier inside a row, or a relation inside another 360.\n *\n * Opening is a step in the address (`f=kind:id`, the same step twin's focus takes), so the dock's back\n * button and the browser's go to the 360 before, and a reload or a shared link opens the same one.\n */\nexport function openEntity360Kind(\n kind: string,\n value: Record<string, any>,\n returnFocus?: () => void,\n /** The element the request comes from; a link inside a popup names that popup as its origin. */\n source?: Element\n): void {\n const subject: Entity360Subject = { kind, id: entity360IdOf(kind, value), value: value ?? {} }\n const address = addressOf(kind, subject.id)\n opened.set(address, subject.value)\n if (returnFocus) returnFocusByAddress.set(address, returnFocus)\n\n if (kind && subject.id) {\n const url = new URL(location.href)\n if (url.searchParams.get(FOCUS) !== address) {\n url.searchParams.set(FOCUS, address)\n void navigateEntity360(url.pathname + url.search + url.hash, source)\n return\n }\n }\n\n /*\n * Already shown - an app that wrote the address itself (twin's focus) opens it again right after the address drew\n * it. Drawing again would hand the sections a new subject, which is how a refresh asks them to read.\n */\n if (shown?.kind === kind && shown.id === subject.id && isEntity360Shown(OWNER)) return\n\n draw(subject, returnFocus)\n}\n\n/*\n * What a 360 section holds unsaved goes when its subject does. A draft keeps it (ADR-0098) - only an entry this\n * browser cannot keep is asked about, with the page's own question, by the address layer's one guard.\n */\naddressController().guard({\n discards: change => (change.focus || change.route) && isEntity360Unkept(OWNER),\n kept: () => false,\n approve: change =>\n !((change.focus || change.route) && isEntity360Unkept(OWNER)) ||\n OxPrompt.open({\n type: 'warning',\n title: i18next.t('title.unsaved-draft', { defaultValue: 'there is an unsaved edit' }),\n text: i18next.t('text.unsaved-may-be-lost', { defaultValue: 'anything not saved is lost.' }),\n confirmButton: { text: i18next.t('button.discard', { defaultValue: 'discard' }), destructive: true },\n cancelButton: { text: i18next.t('button.cancel', { defaultValue: 'cancel' }) },\n signal: change.signal\n })\n})\n\n/* Back and forward walk the address; the dock follows it and adds no step of its own. */\naddressController().subscribe(() => {\n const inAddress = subjectInAddress()\n\n if (inAddress && kinds.has(inAddress.kind)) {\n if (shown?.kind === inAddress.kind && shown.id === inAddress.id && isEntity360Shown(OWNER)) return\n const value = opened.get(addressOf(inAddress.kind, inAddress.id)) ?? { id: inAddress.id }\n draw({ ...inAddress, value }, returnFocusByAddress.get(addressOf(inAddress.kind, inAddress.id)))\n } else if (isEntity360Shown(OWNER)) {\n close(false)\n }\n}, false)\n\n/** Close the registry's 360 and take its step out of the address - for an app whose own state let go of it. */\nexport function closeEntity360(): void {\n if (isEntity360Shown(OWNER)) close(true)\n}\n\n/**\n * What a section has read about its subject - its name, what the title and context are drawn from (ADR-0067\n * decision 6). Opened from an address alone the value is `{ id }`; the section that reads who the subject is gives\n * the rest here, the value is kept for going back to it, and the heading is drawn anew. The sections are left as\n * they are. A subject no longer shown is only remembered.\n */\nexport function setEntity360Value(subject: Pick<Entity360Subject, 'kind' | 'id'>, value: Record<string, any>): void {\n const address = addressOf(subject.kind, subject.id)\n const merged = { ...opened.get(address), ...value, id: subject.id }\n opened.set(address, merged)\n if (shown?.kind === subject.kind && shown.id === subject.id && isEntity360Shown(OWNER)) {\n shown.value = merged\n draw(shown)\n }\n}\n\n/**\n * Reads the shown 360 again when what changed is about it - the header's refresh, asked by whatever wrote the records\n * (an applied AI card). Every section reads its door again and keeps what it holds (ADR-0098); another 360, or none,\n * is left alone. Returns whether one was read again.\n */\nexport function refreshEntity360(about: (subject: Pick<Entity360Subject, 'kind' | 'id'>) => boolean): boolean {\n if (!shown || !isEntity360Shown(OWNER) || !about({ kind: shown.kind, id: shown.id })) return false\n draw({ ...shown })\n return true\n}\n\ntype Entity360Listener = (subject: Entity360Subject | undefined) => void\nconst listeners = new Set<Entity360Listener>()\n\n/**\n * Hear which subject the registry's 360 shows - on open, on back or forward, and `undefined` on close.\n * An app with its own selection (twin's focus) follows the dock through this. Returns the unsubscribe.\n */\nexport function onEntity360Change(listener: Entity360Listener): () => void {\n listeners.add(listener)\n return () => listeners.delete(listener)\n}\n\nfunction tell(subject: Entity360Subject | undefined): void {\n listeners.forEach(listener => listener(subject))\n}\n\nfunction close(clearAddress: boolean): void {\n if (clearAddress && subjectInAddress()) {\n const url = new URL(location.href)\n url.searchParams.delete(FOCUS)\n void writeAddress(url, { replace: true, state: history.state })\n return\n }\n hideEntity360(OWNER)\n shown = undefined\n giveSubjectBack()\n tell(undefined)\n}\n\nfunction draw(subject: Entity360Subject, returnFocus?: () => void): void {\n const { kind, value } = subject\n const declaration = kinds.get(kind)\n const hung = entity360Sections(kind)\n shown = subject\n\n const content = hung.length\n ? html`${hung.map(section => section.render(subject))}`\n : html`<p data-not-yet>${i18next.t('text.entity360-not-yet', { defaultValue: 'there is no 360 for this kind yet.' })}</p>`\n\n showEntity360(\n OWNER,\n {\n key: addressOf(kind, subject.id),\n title: declaration ? declaration.title(subject) : String(value?.name ?? value?.id ?? ''),\n context: declaration?.context ? declaration.context(subject) : '',\n content,\n labels: {\n close: i18next.t('button.close', { defaultValue: 'close' }),\n expand: i18next.t('button.expand', { defaultValue: 'expand' }),\n collapse: i18next.t('button.collapse', { defaultValue: 'collapse' }),\n resize: i18next.t('label.resize', { defaultValue: 'resize' }),\n back: i18next.t('button.back', { defaultValue: 'back' })\n },\n onClose: () => close(true),\n onBack: canGoBackEntity360() ? () => history.back() : undefined,\n /* A new subject of the same kind and id: every section reads its door again and keeps what it holds. */\n onRefresh: () => {\n if (shown === subject) draw({ ...subject })\n }\n },\n returnFocus\n )\n\n if (declaration?.assistantSubject) takeSubject({ type: kind, id: subject.id })\n else giveSubjectBack()\n\n tell(subject)\n}\n\nfunction routeContext(): any {\n return (store.getState() as any)?.route?.context ?? {}\n}\n\nfunction takeSubject(ours: { type: string; id: string }): void {\n const context = routeContext()\n if (!pageSubject || pageSubject.path !== location.pathname) pageSubject = { path: location.pathname, subject: context.subject }\n pageSubject.ours = ours\n store.dispatch({ type: UPDATE_CONTEXT, context: { ...context, subject: ours } })\n}\n\n/** Only on the same page, and only while the subject is still the one the 360 put there. */\nfunction giveSubjectBack(): void {\n const taken = pageSubject\n pageSubject = undefined\n if (!taken || taken.path !== location.pathname) return\n\n const context = routeContext()\n if (context.subject === taken.ours) store.dispatch({ type: UPDATE_CONTEXT, context: { ...context, subject: taken.subject } })\n}\n"]}
1
+ {"version":3,"file":"entity360-registry.js","sourceRoot":"","sources":["../../../src/actions/entity360-registry.ts"],"names":[],"mappings":"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","sourcesContent":["import { html, type TemplateResult } from 'lit'\nimport { i18next } from '@operato/i18n'\nimport { OxPrompt } from '@operato/popup/ox-prompt.js'\nimport { addressController, writeAddress } from '@operato/utils/address.js'\n\nimport { store, UPDATE_CONTEXT } from '@operato/shell'\n\nimport { showEntity360, hideEntity360, canGoBackEntity360, isEntity360Shown, isEntity360Unkept, navigateEntity360, refetchEntity360 } from './entity360.js'\n\nconst returnFocusByAddress = new Map<string, () => void>()\n\n/**\n * Which kinds of thing open a 360, and what each module puts in it (ADR-0067, the person 360 ruling).\n *\n * An entity's name is a link to its 360 (ADR-0067 decision 1), and a grid shows names by the dozen -\n * `updater` alone is a person column on 65 framework screens. Writing an opener into every column\n * would never finish. Instead a kind is declared once with the GraphQL `__typename`s it claims, and\n * whatever draws a value can ask `entity360KindOf(value)`: registered, it is a link; not, it stays\n * text, exactly as before.\n *\n * This file knows no product words and imports no product module. A kind brings its own title; each\n * module hangs its own section with its own read door (`registerEntity360Section`), the way activity\n * panels hang on the activity detail. Opening a kind with no section says so in one line - never a\n * blank dock.\n */\n\n/** The thing a 360 is about: its kind, its id, and the value it was opened from. */\nexport interface Entity360Subject {\n kind: string\n id: string\n value: Record<string, any>\n}\n\nexport interface Entity360KindDeclaration {\n /** The name sections register under - 'person', 'role', … */\n kind: string\n /** The GraphQL `__typename`s whose values open this kind. A typename belongs to one kind. */\n typenames: string[]\n /**\n * The kind's one identifier for a value - what the address, the links and `subject.id` all carry (ADR-0067\n * decision 6). A section reads its door by `subject.id` alone: a reload or a shared link brings nothing else.\n * Without it, `value.id`.\n */\n id?(value: Record<string, any>): string | null | undefined\n /** The dock's title for a subject - what a person recognises it by. */\n title(subject: Entity360Subject): string\n /** A line under the title, if the kind has one. */\n context?(subject: Entity360Subject): string\n /**\n * The open 360 is what the page talks about: while it is open the page context's `subject` is\n * `{ type: kind, id }`, so the AI dock follows it (ai-assistant registerSubjectSurface). Closing\n * gives the page its own subject back. Off by default - a kind the assistant has no surface for\n * would only take the page's subject away.\n */\n assistantSubject?: boolean\n}\n\nexport interface Entity360Section {\n /** Unique within its kind. */\n name: string\n /** Lower comes first; equal orders keep the order they were registered in. */\n order?: number\n /** The section's content - usually the module's own element, which reads its own door. */\n render(subject: Entity360Subject): TemplateResult\n}\n\nexport type Entity360Rejection = { kind: string; name?: string; reason: string }\n\nconst kinds = new Map<string, Entity360KindDeclaration>()\nconst kindOfTypename = new Map<string, string>()\nconst sections = new Map<string, (Entity360Section & { at: number })[]>()\nconst rejected: Entity360Rejection[] = []\nlet registered = 0\n\n/** The dock has one owner for everything opened through this registry. */\nconst OWNER = {}\n\n/**\n * Declare a kind. A kind named twice, or a typename another kind already claims, is refused and\n * kept in `entity360Rejections()` - silently taking the second would reroute links nobody asked to move.\n */\nexport function registerEntity360Kind(declaration: Entity360KindDeclaration): void {\n const kind = String(declaration?.kind ?? '').trim()\n\n if (!kind) return void rejected.push({ kind: '(no kind)', reason: 'no-kind' })\n if (kinds.has(kind)) return void rejected.push({ kind, reason: 'duplicate-kind' })\n if (typeof declaration.title !== 'function') return void rejected.push({ kind, reason: 'no-title' })\n\n const taken = (declaration.typenames || []).find(typename => kindOfTypename.has(typename))\n if (taken) return void rejected.push({ kind, name: taken, reason: 'typename-claimed' })\n\n kinds.set(kind, { ...declaration, kind })\n for (const typename of declaration.typenames || []) kindOfTypename.set(typename, kind)\n}\n\n/** Hang a section on a kind. A name already on that kind is refused. */\nexport function registerEntity360Section(kind: string, section: Entity360Section): void {\n const name = String(section?.name ?? '').trim()\n\n if (!name) return void rejected.push({ kind, reason: 'no-name' })\n if (typeof section.render !== 'function') return void rejected.push({ kind, name, reason: 'no-render' })\n\n const list = sections.get(kind) ?? []\n if (list.some(one => one.name === name)) return void rejected.push({ kind, name, reason: 'duplicate-name' })\n\n list.push({ ...section, name, at: registered++ })\n list.sort((a, b) => (a.order ?? 0) - (b.order ?? 0) || a.at - b.at)\n sections.set(kind, list)\n}\n\n/**\n * The kind a value opens, or `undefined`.\n *\n * `undefined` for anything not registered and for a value without `__typename` (a call that turned\n * the cache off) - the caller draws it as it always has.\n */\nexport function entity360KindOf(value: unknown): string | undefined {\n const typename = (value as any)?.__typename\n return typeof typename === 'string' ? kindOfTypename.get(typename) : undefined\n}\n\n/** The identifier a kind gives a value (its declaration's `id`, else `value.id`); '' when there is none. */\nexport function entity360IdOf(kind: string, value: Record<string, any> | null | undefined): string {\n const declared = kinds.get(kind)?.id\n return String((declared ? declared(value ?? {}) : value?.id) ?? '')\n}\n\n/** Whether a kind was declared - an identifier of an undeclared kind stays text. */\nexport function entity360KindRegistered(kind: string): boolean {\n return kinds.has(kind)\n}\n\n/** The sections hung on a kind, in order. */\nexport function entity360Sections(kind: string): Entity360Section[] {\n return [...(sections.get(kind) ?? [])]\n}\n\n/** What was refused, and why - for a test or a boot check to read. */\nexport function entity360Rejections(): Entity360Rejection[] {\n return [...rejected]\n}\n\nlet shown: Entity360Subject | undefined\n/** Values opened in this page, by address - going back to one gives its section the value it was opened with. */\nconst opened = new Map<string, Record<string, any>>()\n/** The page's own subject while a 360 stands in for it, and the page it belongs to. */\nlet pageSubject: { path: string; subject: unknown; ours?: unknown } | undefined\n\n/** The address of a subject: `f=kind:id`. The id may hold colons (an EPC URN); the kind does not. */\nconst FOCUS = 'f'\nconst addressOf = (kind: string, id: string) => `${kind}:${id}`\n\nfunction subjectInAddress(): { kind: string; id: string } | undefined {\n const value = new URLSearchParams(location.search).get(FOCUS)\n const at = value ? value.indexOf(':') : -1\n return at > 0 ? { kind: value!.slice(0, at), id: value!.slice(at + 1) } : undefined\n}\n\n/**\n * Open the 360 of a value.\n *\n * A kind with sections shows them in order. A kind with none - or a value that opens no kind -\n * shows one line saying there is no 360 for it yet. The dock is never blank.\n */\nexport function openEntity360For(value: Record<string, any>, returnFocus?: () => void, source?: Element): void {\n openEntity360Kind(entity360KindOf(value) ?? '', value, returnFocus, source)\n}\n\n/**\n * Open the 360 of a kind directly - for a value known by its kind and id rather than by a GraphQL\n * typename: an identifier inside a row, or a relation inside another 360.\n *\n * Opening is a step in the address (`f=kind:id`, the same step twin's focus takes), so the dock's back\n * button and the browser's go to the 360 before, and a reload or a shared link opens the same one.\n */\nexport function openEntity360Kind(\n kind: string,\n value: Record<string, any>,\n returnFocus?: () => void,\n /** The element the request comes from; a link inside a popup names that popup as its origin. */\n source?: Element\n): void {\n const subject: Entity360Subject = { kind, id: entity360IdOf(kind, value), value: value ?? {} }\n const address = addressOf(kind, subject.id)\n opened.set(address, subject.value)\n if (returnFocus) returnFocusByAddress.set(address, returnFocus)\n\n if (kind && subject.id) {\n const url = new URL(location.href)\n if (url.searchParams.get(FOCUS) !== address) {\n url.searchParams.set(FOCUS, address)\n void navigateEntity360(url.pathname + url.search + url.hash, source)\n return\n }\n }\n\n /*\n * Already shown - an app that wrote the address itself (twin's focus) opens it again right after the address drew\n * it. Drawing again would hand the sections a new subject, which is how a refresh asks them to read.\n */\n if (shown?.kind === kind && shown.id === subject.id && isEntity360Shown(OWNER)) return\n\n draw(subject, returnFocus)\n}\n\n/*\n * What a 360 section holds unsaved goes when its subject does. A draft keeps it (ADR-0098) - only an entry this\n * browser cannot keep is asked about, with the page's own question, by the address layer's one guard.\n */\naddressController().guard({\n discards: change => (change.focus || change.route) && isEntity360Unkept(OWNER),\n kept: () => false,\n approve: change =>\n !((change.focus || change.route) && isEntity360Unkept(OWNER)) ||\n OxPrompt.open({\n type: 'warning',\n title: i18next.t('title.unsaved-draft', { defaultValue: 'there is an unsaved edit' }),\n text: i18next.t('text.unsaved-may-be-lost', { defaultValue: 'anything not saved is lost.' }),\n confirmButton: { text: i18next.t('button.discard', { defaultValue: 'discard' }), destructive: true },\n cancelButton: { text: i18next.t('button.cancel', { defaultValue: 'cancel' }) },\n signal: change.signal\n })\n})\n\n/* Back and forward walk the address; the dock follows it and adds no step of its own. */\naddressController().subscribe(() => {\n const inAddress = subjectInAddress()\n\n if (inAddress && kinds.has(inAddress.kind)) {\n if (shown?.kind === inAddress.kind && shown.id === inAddress.id && isEntity360Shown(OWNER)) return\n const value = opened.get(addressOf(inAddress.kind, inAddress.id)) ?? { id: inAddress.id }\n draw({ ...inAddress, value }, returnFocusByAddress.get(addressOf(inAddress.kind, inAddress.id)))\n } else if (isEntity360Shown(OWNER)) {\n close(false)\n }\n}, false)\n\n/** Close the registry's 360 and take its step out of the address - for an app whose own state let go of it. */\nexport function closeEntity360(): void {\n if (isEntity360Shown(OWNER)) close(true)\n}\n\n/**\n * What a section has read about its subject - its name, what the title and context are drawn from (ADR-0067\n * decision 6). Opened from an address alone the value is `{ id }`; the section that reads who the subject is gives\n * the rest here, the value is kept for going back to it, and the heading is drawn anew. The sections are left as\n * they are. A subject no longer shown is only remembered.\n */\nexport function setEntity360Value(subject: Pick<Entity360Subject, 'kind' | 'id'>, value: Record<string, any>): void {\n const address = addressOf(subject.kind, subject.id)\n const merged = { ...opened.get(address), ...value, id: subject.id }\n opened.set(address, merged)\n if (shown?.kind === subject.kind && shown.id === subject.id && isEntity360Shown(OWNER)) {\n shown.value = merged\n draw(shown)\n }\n}\n\n/**\n * Reads the shown 360 again when what changed is about it - the header's refresh, asked by whatever wrote the records\n * (an applied AI card). Every section reads its door again and keeps what it holds (ADR-0098); another 360, or none,\n * is left alone. Returns whether one was read again.\n */\nexport function refreshEntity360(about: (subject: Pick<Entity360Subject, 'kind' | 'id'>) => boolean): boolean {\n if (!shown || !isEntity360Shown(OWNER) || !about({ kind: shown.kind, id: shown.id })) return false\n draw({ ...shown })\n refetchEntity360(OWNER)\n return true\n}\n\ntype Entity360Listener = (subject: Entity360Subject | undefined) => void\nconst listeners = new Set<Entity360Listener>()\n\n/**\n * Hear which subject the registry's 360 shows - on open, on back or forward, and `undefined` on close.\n * An app with its own selection (twin's focus) follows the dock through this. Returns the unsubscribe.\n */\nexport function onEntity360Change(listener: Entity360Listener): () => void {\n listeners.add(listener)\n return () => listeners.delete(listener)\n}\n\nfunction tell(subject: Entity360Subject | undefined): void {\n listeners.forEach(listener => listener(subject))\n}\n\nfunction close(clearAddress: boolean): void {\n if (clearAddress && subjectInAddress()) {\n const url = new URL(location.href)\n url.searchParams.delete(FOCUS)\n void writeAddress(url, { replace: true, state: history.state })\n return\n }\n hideEntity360(OWNER)\n shown = undefined\n giveSubjectBack()\n tell(undefined)\n}\n\nfunction draw(subject: Entity360Subject, returnFocus?: () => void): void {\n const { kind, value } = subject\n const declaration = kinds.get(kind)\n const hung = entity360Sections(kind)\n shown = subject\n\n const content = hung.length\n ? html`${hung.map(section => section.render(subject))}`\n : html`<p data-not-yet>${i18next.t('text.entity360-not-yet', { defaultValue: 'there is no 360 for this kind yet.' })}</p>`\n\n showEntity360(\n OWNER,\n {\n key: addressOf(kind, subject.id),\n title: declaration ? declaration.title(subject) : String(value?.name ?? value?.id ?? ''),\n context: declaration?.context ? declaration.context(subject) : '',\n content,\n labels: {\n close: i18next.t('button.close', { defaultValue: 'close' }),\n expand: i18next.t('button.expand', { defaultValue: 'expand' }),\n collapse: i18next.t('button.collapse', { defaultValue: 'collapse' }),\n resize: i18next.t('label.resize', { defaultValue: 'resize' }),\n back: i18next.t('button.back', { defaultValue: 'back' })\n },\n onClose: () => close(true),\n onBack: canGoBackEntity360() ? () => history.back() : undefined,\n /* A new subject of the same kind and id: every section reads its door again and keeps what it holds. */\n onRefresh: () => {\n if (shown !== subject) return\n draw({ ...subject })\n refetchEntity360(OWNER)\n }\n },\n returnFocus\n )\n\n if (declaration?.assistantSubject) takeSubject({ type: kind, id: subject.id })\n else giveSubjectBack()\n\n tell(subject)\n}\n\nfunction routeContext(): any {\n return (store.getState() as any)?.route?.context ?? {}\n}\n\nfunction takeSubject(ours: { type: string; id: string }): void {\n const context = routeContext()\n if (!pageSubject || pageSubject.path !== location.pathname) pageSubject = { path: location.pathname, subject: context.subject }\n pageSubject.ours = ours\n store.dispatch({ type: UPDATE_CONTEXT, context: { ...context, subject: ours } })\n}\n\n/** Only on the same page, and only while the subject is still the one the 360 put there. */\nfunction giveSubjectBack(): void {\n const taken = pageSubject\n pageSubject = undefined\n if (!taken || taken.path !== location.pathname) return\n\n const context = routeContext()\n if (context.subject === taken.ours) store.dispatch({ type: UPDATE_CONTEXT, context: { ...context, subject: taken.subject } })\n}\n"]}
@@ -1,6 +1,8 @@
1
1
  import { type Entity360View } from '../components/ox-entity360-dock.js';
2
2
  /** One dock per application, independent of product controllers and their render lifetimes. */
3
3
  export declare function showEntity360(owner: object, view: Entity360View, returnFocus?: () => void): void;
4
+ /** The owner's open 360 reads again what its sections declared they read (ADR-0190) - the header's refresh. */
5
+ export declare function refetchEntity360(owner: object): void;
4
6
  /** Whether the dock is open for this owner - closed by its button, by another owner or by navigation, it is not. */
5
7
  export declare function isEntity360Shown(owner: object): boolean;
6
8
  /** Whether the owner's open 360 holds an entry no draft keeps (ADR-0067 decision 8). */
@@ -2,6 +2,7 @@ import { html } from 'lit';
2
2
  import { store, REGISTER_NAVIGATION_CALLBACK } from '@operato/shell';
3
3
  import { appendViewpart, removeViewpart, VIEWPART_POSITION } from './layout.js';
4
4
  import { writeAddress } from '@operato/utils/address.js';
5
+ import { refetchWithin } from '@operato/graphql';
5
6
  const NAME = 'entity360';
6
7
  let current;
7
8
  // Cached pages may stay connected after navigation; their details must not leak into another task.
@@ -26,6 +27,11 @@ export function showEntity360(owner, view, returnFocus) {
26
27
  current.returnFocus = returnFocus;
27
28
  }
28
29
  }
30
+ /** The owner's open 360 reads again what its sections declared they read (ADR-0190) - the header's refresh. */
31
+ export function refetchEntity360(owner) {
32
+ if ((current === null || current === void 0 ? void 0 : current.owner) === owner)
33
+ void refetchWithin(current.dock);
34
+ }
29
35
  /** Whether the dock is open for this owner - closed by its button, by another owner or by navigation, it is not. */
30
36
  export function isEntity360Shown(owner) {
31
37
  return (current === null || current === void 0 ? void 0 : current.owner) === owner;
@@ -1 +1 @@
1
- {"version":3,"file":"entity360.js","sourceRoot":"","sources":["../../../src/actions/entity360.ts"],"names":[],"mappings":"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","sourcesContent":["import { html } from 'lit'\nimport { store, REGISTER_NAVIGATION_CALLBACK } from '@operato/shell'\nimport { appendViewpart, removeViewpart, VIEWPART_POSITION } from './layout.js'\nimport { OxEntity360Dock, type Entity360View } from '../components/ox-entity360-dock.js'\nimport { writeAddress } from '@operato/utils/address.js'\n\nconst NAME = 'entity360'\nlet current: { owner: object; dock: OxEntity360Dock; path: string; returnFocus?: () => void } | undefined\n\n// Cached pages may stay connected after navigation; their details must not leak into another task.\nstore.dispatch({ type: REGISTER_NAVIGATION_CALLBACK, callback: () => {\n if (current && current.path !== location.pathname) hideEntity360(current.owner, false)\n} })\n\n/** One dock per application, independent of product controllers and their render lifetimes. */\nexport function showEntity360(owner: object, view: Entity360View, returnFocus?: () => void): void {\n if (current?.owner !== owner) {\n if (current) hideEntity360(current.owner, false)\n const dock = document.createElement('ox-entity360-dock') as OxEntity360Dock\n current = { owner, dock, path: location.pathname, returnFocus }\n dock.view = view\n appendViewpart({ name: NAME, position: VIEWPART_POSITION.ASIDEBAR,\n viewpart: { show: true, persistent: true, template: html`${dock}` } })\n } else {\n current.dock.view = view\n if (returnFocus) current.returnFocus = returnFocus\n }\n}\n\n/** Whether the dock is open for this owner - closed by its button, by another owner or by navigation, it is not. */\nexport function isEntity360Shown(owner: object): boolean {\n return current?.owner === owner\n}\n\n/** Whether the owner's open 360 holds an entry no draft keeps (ADR-0067 decision 8). */\nexport function isEntity360Unkept(owner: object): boolean {\n return current?.owner === owner && current.dock.unkept\n}\n\nexport function hideEntity360(owner: object, restoreFocus = true): void {\n if (current?.owner !== owner) return\n const previous = current\n current = undefined\n removeViewpart(NAME)\n if (restoreFocus) queueMicrotask(() => previous.returnFocus?.())\n}\n\n/**\n * Providers validate the canonical axis and read scope before calling this function. `source` is the element the\n * request comes from: a link inside a popup names that popup as its origin.\n */\nexport function navigateEntity360(address: string, source?: Element): Promise<boolean> {\n const url = new URL(address, location.href)\n if (url.origin !== location.origin) throw new Error('Entity 360 must stay within the application')\n if (url.href === location.href) return Promise.resolve(true)\n return writeAddress(url, { state: { ...history.state, entity360Previous: location.href }, source })\n}\n\nexport function canGoBackEntity360(): boolean {\n const previous = history.state?.entity360Previous\n if (typeof previous !== 'string') return false\n const url = new URL(previous, location.href)\n return url.pathname === location.pathname && url.searchParams.has('f')\n}\n"]}
1
+ {"version":3,"file":"entity360.js","sourceRoot":"","sources":["../../../src/actions/entity360.ts"],"names":[],"mappings":"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","sourcesContent":["import { html } from 'lit'\nimport { store, REGISTER_NAVIGATION_CALLBACK } from '@operato/shell'\nimport { appendViewpart, removeViewpart, VIEWPART_POSITION } from './layout.js'\nimport { OxEntity360Dock, type Entity360View } from '../components/ox-entity360-dock.js'\nimport { writeAddress } from '@operato/utils/address.js'\nimport { refetchWithin } from '@operato/graphql'\n\nconst NAME = 'entity360'\nlet current: { owner: object; dock: OxEntity360Dock; path: string; returnFocus?: () => void } | undefined\n\n// Cached pages may stay connected after navigation; their details must not leak into another task.\nstore.dispatch({ type: REGISTER_NAVIGATION_CALLBACK, callback: () => {\n if (current && current.path !== location.pathname) hideEntity360(current.owner, false)\n} })\n\n/** One dock per application, independent of product controllers and their render lifetimes. */\nexport function showEntity360(owner: object, view: Entity360View, returnFocus?: () => void): void {\n if (current?.owner !== owner) {\n if (current) hideEntity360(current.owner, false)\n const dock = document.createElement('ox-entity360-dock') as OxEntity360Dock\n current = { owner, dock, path: location.pathname, returnFocus }\n dock.view = view\n appendViewpart({ name: NAME, position: VIEWPART_POSITION.ASIDEBAR,\n viewpart: { show: true, persistent: true, template: html`${dock}` } })\n } else {\n current.dock.view = view\n if (returnFocus) current.returnFocus = returnFocus\n }\n}\n\n/** The owner's open 360 reads again what its sections declared they read (ADR-0190) - the header's refresh. */\nexport function refetchEntity360(owner: object): void {\n if (current?.owner === owner) void refetchWithin(current.dock)\n}\n\n/** Whether the dock is open for this owner - closed by its button, by another owner or by navigation, it is not. */\nexport function isEntity360Shown(owner: object): boolean {\n return current?.owner === owner\n}\n\n/** Whether the owner's open 360 holds an entry no draft keeps (ADR-0067 decision 8). */\nexport function isEntity360Unkept(owner: object): boolean {\n return current?.owner === owner && current.dock.unkept\n}\n\nexport function hideEntity360(owner: object, restoreFocus = true): void {\n if (current?.owner !== owner) return\n const previous = current\n current = undefined\n removeViewpart(NAME)\n if (restoreFocus) queueMicrotask(() => previous.returnFocus?.())\n}\n\n/**\n * Providers validate the canonical axis and read scope before calling this function. `source` is the element the\n * request comes from: a link inside a popup names that popup as its origin.\n */\nexport function navigateEntity360(address: string, source?: Element): Promise<boolean> {\n const url = new URL(address, location.href)\n if (url.origin !== location.origin) throw new Error('Entity 360 must stay within the application')\n if (url.href === location.href) return Promise.resolve(true)\n return writeAddress(url, { state: { ...history.state, entity360Previous: location.href }, source })\n}\n\nexport function canGoBackEntity360(): boolean {\n const previous = history.state?.entity360Previous\n if (typeof previous !== 'string') return false\n const url = new URL(previous, location.href)\n return url.pathname === location.pathname && url.searchParams.has('f')\n}\n"]}
@@ -28,7 +28,6 @@ export interface Entity360View {
28
28
  * A section tells the dock it holds an entry this browser cannot keep as a draft (ADR-0067 decision 8, ADR-0098
29
29
  * decision 5): `detail.unkept` true while so, false once saved, discarded or kept. Only then is leaving asked about.
30
30
  */
31
- export declare const ENTITY360_UNKEPT = "entity360-unkept";
32
31
  /** Product CSS stays inside this boundary, never changes the dock's layout or controls. */
33
32
  export declare class OxEntity360Content extends LitElement {
34
33
  view?: Entity360View;
@@ -4,13 +4,13 @@ import { LitElement, css, html } from 'lit';
4
4
  import { customElement, property, state } from 'lit/decorators.js';
5
5
  import { keyed } from 'lit/directives/keyed.js';
6
6
  import { i18next } from '@operato/i18n';
7
+ import { DRAFT_UNKEPT } from '@operato/shell';
7
8
  import { ifDefined } from 'lit/directives/if-defined.js';
8
9
  import { ScrollbarStyles } from '@operato/styles';
9
10
  /**
10
11
  * A section tells the dock it holds an entry this browser cannot keep as a draft (ADR-0067 decision 8, ADR-0098
11
12
  * decision 5): `detail.unkept` true while so, false once saved, discarded or kept. Only then is leaving asked about.
12
13
  */
13
- export const ENTITY360_UNKEPT = 'entity360-unkept';
14
14
  /** Product CSS stays inside this boundary, never changes the dock's layout or controls. */
15
15
  let OxEntity360Content = class OxEntity360Content extends LitElement {
16
16
  render() {
@@ -78,12 +78,12 @@ let OxEntity360Dock = class OxEntity360Dock extends LitElement {
78
78
  super.connectedCallback();
79
79
  document.addEventListener('keydown', this.onEscape);
80
80
  this.media.addEventListener('change', this.onMedia);
81
- this.addEventListener(ENTITY360_UNKEPT, this.onUnkept);
81
+ this.addEventListener(DRAFT_UNKEPT, this.onUnkept);
82
82
  }
83
83
  disconnectedCallback() {
84
84
  document.removeEventListener('keydown', this.onEscape);
85
85
  this.media.removeEventListener('change', this.onMedia);
86
- this.removeEventListener(ENTITY360_UNKEPT, this.onUnkept);
86
+ this.removeEventListener(DRAFT_UNKEPT, this.onUnkept);
87
87
  super.disconnectedCallback();
88
88
  }
89
89
  firstUpdated() { this.focusHeading(); }
@@ -1 +1 @@
1
- {"version":3,"file":"ox-entity360-dock.js","sourceRoot":"","sources":["../../../src/components/ox-entity360-dock.ts"],"names":[],"mappings":"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","sourcesContent":["import '@material/web/icon/icon.js'\nimport { LitElement, css, html, type CSSResult, type TemplateResult } from 'lit'\nimport { customElement, property, state } from 'lit/decorators.js'\nimport { keyed } from 'lit/directives/keyed.js'\nimport { i18next } from '@operato/i18n'\nimport { ifDefined } from 'lit/directives/if-defined.js'\nimport { ScrollbarStyles } from '@operato/styles'\n\n/** Products supply authorized contents and words; the shared dock owns presentation only. */\nexport interface Entity360View {\n /** Canonical subject + read scope, not a display name. Different revisions must have different keys. */\n key: string\n title: string\n context: string\n content: TemplateResult\n styles?: CSSResult[]\n labels: { close: string; expand: string; collapse: string; resize: string; back: string; refresh?: string }\n onClose(): void\n onBack?: () => void\n /**\n * Read the same subject again (ADR-0067 decision 8). The contents stay - what a section is writing stays with it;\n * each section asks its door anew. Without it the refresh button is not drawn.\n */\n onRefresh?: () => void\n}\n\n/**\n * A section tells the dock it holds an entry this browser cannot keep as a draft (ADR-0067 decision 8, ADR-0098\n * decision 5): `detail.unkept` true while so, false once saved, discarded or kept. Only then is leaving asked about.\n */\nexport const ENTITY360_UNKEPT = 'entity360-unkept'\n\n/** Product CSS stays inside this boundary, never changes the dock's layout or controls. */\n@customElement('ox-entity360-content')\nexport class OxEntity360Content extends LitElement {\n @property({ attribute: false }) view?: Entity360View\n render() {\n return html`<style>${(this.view?.styles ?? []).map(style => style.cssText).join('\\n')}</style>${keyed(this.view?.key, this.view?.content)}`\n }\n}\n\n@customElement('ox-entity360-dock')\nexport class OxEntity360Dock extends LitElement {\n /** The sections that hold an entry no draft keeps. */\n private unkeptBy = new Set<EventTarget>()\n private onUnkept = (event: Event) => {\n const section = event.composedPath()[0]\n if ((event as CustomEvent<{ unkept: boolean }>).detail?.unkept) this.unkeptBy.add(section)\n else this.unkeptBy.delete(section)\n }\n /** Whether leaving this subject would lose an entry without a word - a draft keeps all the others. */\n get unkept(): boolean {\n for (const section of this.unkeptBy) if (!(section as Node).isConnected) this.unkeptBy.delete(section)\n return this.unkeptBy.size > 0\n }\n static styles = [ScrollbarStyles, css`\n :host { display: block; width: var(--entity360-width, 480px); max-width: 65vw; height: 100%; min-height: 0;\n color: var(--md-sys-color-on-surface, #222); background: var(--md-sys-color-surface, white);\n border-left: 1px solid var(--ui-line, #ccc); box-sizing: border-box; position: relative; }\n :host([expanded]) { position: fixed; inset: 0; width: 100vw; max-width: none; height: 100dvh; z-index: 901; }\n aside { height: 100%; display: flex; flex-direction: column; min-height: 0; }\n header { display: flex; align-items: flex-start; gap: 6px; padding: 12px 14px;\n border-bottom: 1px solid var(--ui-line, #ccc); flex: none; }\n .heading { flex: 1; min-width: 0; }\n h2 { font: 600 15px/1.4 var(--ui-font-family, sans-serif); margin: 0; overflow-wrap: anywhere; }\n h2:focus { outline: none; }\n p { font: 12px/1.4 var(--ui-font-family, sans-serif); margin: 4px 0 0; overflow-wrap: anywhere; color: var(--md-sys-color-on-surface-variant, #555); }\n button { font: 14px/1 sans-serif; width: 28px; height: 28px; padding: 0; display: grid; place-items: center;\n flex: none; cursor: pointer; border: none;\n border-radius: 6px; background: transparent; color: inherit; }\n p:empty { display: none; }\n button md-icon { --md-icon-size: 20px; }\n button:hover { background: var(--md-sys-color-surface-container-high, #eee); }\n button:focus-visible, [role=separator]:focus-visible { outline: 2px solid var(--md-sys-color-primary, #2865ad); outline-offset: -2px; }\n .body { overflow: auto; scrollbar-gutter: stable; min-height: 0; flex: 1; overscroll-behavior: contain; }\n .resize { position: absolute; left: -3px; top: 0; bottom: 0; width: 7px; cursor: col-resize; touch-action: none; z-index: 1; }\n :host([expanded]) .resize { display: none; }\n @media (max-width: 900px) {\n :host { position: fixed; inset: 0; width: 100vw; max-width: none; height: 100dvh; z-index: 901; }\n .resize, .expand { display: none; }\n }\n :host([contained]) { position: relative; inset: auto; width: 100%; max-width: none; height: 100%; z-index: auto; }\n @media (pointer: coarse) { button { width: 40px; height: 40px; } }\n `]\n @property({ attribute: false }) view?: Entity360View\n @property({ type: Boolean, reflect: true }) expanded = false\n /** The aside workspace owns layout and keyboard boundaries while a companion is visible. */\n @property({ type: Boolean, reflect: true }) contained = false\n @state() private width = 480\n private media = matchMedia('(max-width: 900px)')\n @state() private compact = this.media.matches\n private onMedia = () => { this.compact = this.media.matches }\n get fullscreen(): boolean { return this.expanded || this.compact }\n private dragStart?: { x: number; width: number }\n private onEscape = (event: KeyboardEvent) => {\n // A modal opened above the nonmodal dock owns Escape until it closes.\n if (this.contained || event.defaultPrevented || history.state?.overlay) return\n if (event.key === 'Escape') this.view?.onClose()\n if (event.key === 'Tab' && (this.expanded || this.compact)) {\n const walk = (root: ParentNode): HTMLElement[] => Array.from(root.querySelectorAll<HTMLElement>('*'))\n .flatMap(el => [el, ...(el.shadowRoot ? walk(el.shadowRoot) : [])])\n const controls = walk(this.renderRoot).filter(el => el.matches('button,a[href],input,select,textarea,summary,[tabindex]') &&\n el.getAttribute('tabindex') !== '-1' && !el.hasAttribute('disabled') && el.getClientRects().length && getComputedStyle(el).visibility !== 'hidden')\n const first = controls[0], last = controls[controls.length - 1]\n const target = event.composedPath()[0]\n if (!controls.includes(target as HTMLElement) || (event.shiftKey ? target === first : target === last)) {\n event.preventDefault()\n ;(event.shiftKey ? last : first)?.focus()\n }\n }\n }\n connectedCallback() {\n super.connectedCallback()\n document.addEventListener('keydown', this.onEscape)\n this.media.addEventListener('change', this.onMedia)\n this.addEventListener(ENTITY360_UNKEPT, this.onUnkept)\n }\n disconnectedCallback() {\n document.removeEventListener('keydown', this.onEscape)\n this.media.removeEventListener('change', this.onMedia)\n this.removeEventListener(ENTITY360_UNKEPT, this.onUnkept)\n super.disconnectedCallback()\n }\n firstUpdated() { this.focusHeading() }\n updated(changes: Map<PropertyKey, unknown>) {\n if (changes.has('expanded') || changes.has('compact') || changes.has('view'))\n this.dispatchEvent(new CustomEvent('entity360-presentation', { bubbles: true, composed: true }))\n const previous = changes.get('view') as Entity360View | undefined\n if (changes.has('view') && previous?.key !== this.view?.key) {\n this.renderRoot.querySelector('.body')?.scrollTo(0, 0)\n }\n }\n focusHeading() { this.renderRoot.querySelector<HTMLElement>('h2')?.focus({ preventScroll: true }) }\n private resize(width: number) {\n this.width = Math.max(320, Math.min(width, innerWidth * .65, 960))\n this.style.setProperty('--entity360-width', `${this.width}px`)\n }\n render() {\n const view = this.view\n if (!view) return html``\n const fullscreen = !this.contained && this.fullscreen\n return html`<aside role=${fullscreen ? 'dialog' : 'complementary'} aria-modal=${ifDefined(fullscreen ? 'true' : undefined)} aria-labelledby=\"entity360-title\">\n <div class=\"resize\" role=\"separator\" tabindex=\"0\" aria-orientation=\"vertical\"\n aria-label=${view.labels.resize} aria-valuemin=\"320\" aria-valuemax=${Math.min(innerWidth * .65, 960)} aria-valuenow=${this.width}\n @keydown=${(event: KeyboardEvent) => {\n if (!['ArrowLeft', 'ArrowRight', 'Home', 'End'].includes(event.key)) return\n event.preventDefault()\n this.resize(event.key === 'Home' ? 320 : event.key === 'End' ? 960 : this.width + (event.key === 'ArrowLeft' ? 32 : -32))\n }}\n @pointerdown=${(event: PointerEvent) => {\n if (event.button !== 0) return\n this.dragStart = { x: event.clientX, width: this.getBoundingClientRect().width }\n ;(event.currentTarget as HTMLElement).setPointerCapture(event.pointerId)\n }}\n @pointermove=${(event: PointerEvent) => { if (this.dragStart) this.resize(this.dragStart.width + this.dragStart.x - event.clientX) }}\n @pointerup=${() => { this.dragStart = undefined }} @pointercancel=${() => { this.dragStart = undefined }}\n ></div>\n <header>\n ${view.onBack ? html`<button data-action=\"back\" aria-label=${view.labels.back} title=${view.labels.back} @click=${view.onBack}><md-icon aria-hidden=\"true\">arrow_back</md-icon></button>` : ''}\n <div class=\"heading\" aria-live=\"polite\"><h2 id=\"entity360-title\" tabindex=\"-1\">${view.title}</h2><p>${view.context}</p></div>\n ${view.onRefresh ? html`<button data-action=\"refresh\" aria-label=${view.labels.refresh ?? i18next.t('button.refresh')}\n title=${view.labels.refresh ?? i18next.t('button.refresh')} @click=${view.onRefresh}><md-icon aria-hidden=\"true\">refresh</md-icon></button>` : ''}\n <button class=\"expand\" data-action=\"expand\" aria-label=${this.expanded ? view.labels.collapse : view.labels.expand}\n title=${this.expanded ? view.labels.collapse : view.labels.expand} aria-pressed=${this.expanded}\n @click=${() => { this.expanded = !this.expanded }}><md-icon aria-hidden=\"true\">${this.expanded ? 'fullscreen_exit' : 'fullscreen'}</md-icon></button>\n <button data-action=\"close\" aria-label=${view.labels.close} title=${view.labels.close} @click=${view.onClose}><md-icon aria-hidden=\"true\">close</md-icon></button>\n </header>\n <div class=\"body\"><ox-entity360-content .view=${view}></ox-entity360-content></div>\n </aside>`\n }\n}\n"]}
1
+ {"version":3,"file":"ox-entity360-dock.js","sourceRoot":"","sources":["../../../src/components/ox-entity360-dock.ts"],"names":[],"mappings":";AAAA,OAAO,4BAA4B,CAAA;AACnC,OAAO,EAAE,UAAU,EAAE,GAAG,EAAE,IAAI,EAAuC,MAAM,KAAK,CAAA;AAChF,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAA;AAClE,OAAO,EAAE,KAAK,EAAE,MAAM,yBAAyB,CAAA;AAC/C,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAA;AACvC,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAA;AAC7C,OAAO,EAAE,SAAS,EAAE,MAAM,8BAA8B,CAAA;AACxD,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAA;AAoBjD;;;GAGG;AAEH,2FAA2F;AAEpF,IAAM,kBAAkB,GAAxB,MAAM,kBAAmB,SAAQ,UAAU;IAEhD,MAAM;;QACJ,OAAO,IAAI,CAAA,UAAU,CAAC,MAAA,MAAA,IAAI,CAAC,IAAI,0CAAE,MAAM,mCAAI,EAAE,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,KAAK,CAAC,MAAA,IAAI,CAAC,IAAI,0CAAE,GAAG,EAAE,MAAA,IAAI,CAAC,IAAI,0CAAE,OAAO,CAAC,EAAE,CAAA;IAC7I,CAAC;CACF,CAAA;AAJiC;IAA/B,QAAQ,CAAC,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC;gDAAqB;AADzC,kBAAkB;IAD9B,aAAa,CAAC,sBAAsB,CAAC;GACzB,kBAAkB,CAK9B;;AAGM,IAAM,eAAe,GAArB,MAAM,eAAgB,SAAQ,UAAU;IAAxC;;QACL,sDAAsD;QAC9C,aAAQ,GAAG,IAAI,GAAG,EAAe,CAAA;QACjC,aAAQ,GAAG,CAAC,KAAY,EAAE,EAAE;;YAClC,MAAM,OAAO,GAAG,KAAK,CAAC,YAAY,EAAE,CAAC,CAAC,CAAC,CAAA;YACvC,IAAI,MAAC,KAA0C,CAAC,MAAM,0CAAE,MAAM;gBAAE,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,CAAA;;gBACrF,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,OAAO,CAAC,CAAA;QACpC,CAAC,CAAA;QAoC2C,aAAQ,GAAG,KAAK,CAAA;QAC5D,4FAA4F;QAChD,cAAS,GAAG,KAAK,CAAA;QAC5C,UAAK,GAAG,GAAG,CAAA;QACpB,UAAK,GAAG,UAAU,CAAC,oBAAoB,CAAC,CAAA;QAC/B,YAAO,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAA;QACrC,YAAO,GAAG,GAAG,EAAE,GAAG,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAA,CAAC,CAAC,CAAA;QAGrD,aAAQ,GAAG,CAAC,KAAoB,EAAE,EAAE;;YAC1C,sEAAsE;YACtE,IAAI,IAAI,CAAC,SAAS,IAAI,KAAK,CAAC,gBAAgB,KAAI,MAAA,OAAO,CAAC,KAAK,0CAAE,OAAO,CAAA;gBAAE,OAAM;YAC9E,IAAI,KAAK,CAAC,GAAG,KAAK,QAAQ;gBAAE,MAAA,IAAI,CAAC,IAAI,0CAAE,OAAO,EAAE,CAAA;YAChD,IAAI,KAAK,CAAC,GAAG,KAAK,KAAK,IAAI,CAAC,IAAI,CAAC,QAAQ,IAAI,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;gBAC3D,MAAM,IAAI,GAAG,CAAC,IAAgB,EAAiB,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,gBAAgB,CAAc,GAAG,CAAC,CAAC;qBAClG,OAAO,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,GAAG,CAAC,EAAE,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAA;gBACrE,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,OAAO,CAAC,yDAAyD,CAAC;oBACvH,EAAE,CAAC,YAAY,CAAC,UAAU,CAAC,KAAK,IAAI,IAAI,CAAC,EAAE,CAAC,YAAY,CAAC,UAAU,CAAC,IAAI,EAAE,CAAC,cAAc,EAAE,CAAC,MAAM,IAAI,gBAAgB,CAAC,EAAE,CAAC,CAAC,UAAU,KAAK,QAAQ,CAAC,CAAA;gBACrJ,MAAM,KAAK,GAAG,QAAQ,CAAC,CAAC,CAAC,EAAE,IAAI,GAAG,QAAQ,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,CAAA;gBAC/D,MAAM,MAAM,GAAG,KAAK,CAAC,YAAY,EAAE,CAAC,CAAC,CAAC,CAAA;gBACtC,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,MAAqB,CAAC,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,KAAK,KAAK,CAAC,CAAC,CAAC,MAAM,KAAK,IAAI,CAAC,EAAE,CAAC;oBACvG,KAAK,CAAC,cAAc,EAAE,CACrB;oBAAA,MAAA,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,0CAAE,KAAK,EAAE,CAAA;gBAC3C,CAAC;YACH,CAAC;QACH,CAAC,CAAA;IA4DH,CAAC;IAxHC,sGAAsG;IACtG,IAAI,MAAM;QACR,KAAK,MAAM,OAAO,IAAI,IAAI,CAAC,QAAQ;YAAE,IAAI,CAAE,OAAgB,CAAC,WAAW;gBAAE,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,OAAO,CAAC,CAAA;QACtG,OAAO,IAAI,CAAC,QAAQ,CAAC,IAAI,GAAG,CAAC,CAAA;IAC/B,CAAC;IAsCD,IAAI,UAAU,KAAc,OAAO,IAAI,CAAC,QAAQ,IAAI,IAAI,CAAC,OAAO,CAAA,CAAC,CAAC;IAmBlE,iBAAiB;QACf,KAAK,CAAC,iBAAiB,EAAE,CAAA;QACzB,QAAQ,CAAC,gBAAgB,CAAC,SAAS,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAA;QACnD,IAAI,CAAC,KAAK,CAAC,gBAAgB,CAAC,QAAQ,EAAE,IAAI,CAAC,OAAO,CAAC,CAAA;QACnD,IAAI,CAAC,gBAAgB,CAAC,YAAY,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAA;IACpD,CAAC;IACD,oBAAoB;QAClB,QAAQ,CAAC,mBAAmB,CAAC,SAAS,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAA;QACtD,IAAI,CAAC,KAAK,CAAC,mBAAmB,CAAC,QAAQ,EAAE,IAAI,CAAC,OAAO,CAAC,CAAA;QACtD,IAAI,CAAC,mBAAmB,CAAC,YAAY,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAA;QACrD,KAAK,CAAC,oBAAoB,EAAE,CAAA;IAC9B,CAAC;IACD,YAAY,KAAK,IAAI,CAAC,YAAY,EAAE,CAAA,CAAC,CAAC;IACtC,OAAO,CAAC,OAAkC;;QACxC,IAAI,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC,IAAI,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,IAAI,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC;YAC1E,IAAI,CAAC,aAAa,CAAC,IAAI,WAAW,CAAC,wBAAwB,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,CAAA;QAClG,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,MAAM,CAA8B,CAAA;QACjE,IAAI,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAA,QAAQ,aAAR,QAAQ,uBAAR,QAAQ,CAAE,GAAG,OAAK,MAAA,IAAI,CAAC,IAAI,0CAAE,GAAG,CAAA,EAAE,CAAC;YAC5D,MAAA,IAAI,CAAC,UAAU,CAAC,aAAa,CAAC,OAAO,CAAC,0CAAE,QAAQ,CAAC,CAAC,EAAE,CAAC,CAAC,CAAA;QACxD,CAAC;IACH,CAAC;IACD,YAAY,aAAK,MAAA,IAAI,CAAC,UAAU,CAAC,aAAa,CAAc,IAAI,CAAC,0CAAE,KAAK,CAAC,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAA,CAAC,CAAC;IAC3F,MAAM,CAAC,KAAa;QAC1B,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,UAAU,GAAG,GAAG,EAAE,GAAG,CAAC,CAAC,CAAA;QAClE,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,mBAAmB,EAAE,GAAG,IAAI,CAAC,KAAK,IAAI,CAAC,CAAA;IAChE,CAAC;IACD,MAAM;;QACJ,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAA;QACtB,IAAI,CAAC,IAAI;YAAE,OAAO,IAAI,CAAA,EAAE,CAAA;QACxB,MAAM,UAAU,GAAG,CAAC,IAAI,CAAC,SAAS,IAAI,IAAI,CAAC,UAAU,CAAA;QACrD,OAAO,IAAI,CAAA,eAAe,UAAU,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,eAAe,eAAe,SAAS,CAAC,UAAU,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC;;qBAEzG,IAAI,CAAC,MAAM,CAAC,MAAM,sCAAsC,IAAI,CAAC,GAAG,CAAC,UAAU,GAAG,GAAG,EAAE,GAAG,CAAC,kBAAkB,IAAI,CAAC,KAAK;mBACrH,CAAC,KAAoB,EAAE,EAAE;YAClC,IAAI,CAAC,CAAC,WAAW,EAAE,YAAY,EAAE,MAAM,EAAE,KAAK,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC;gBAAE,OAAM;YAC3E,KAAK,CAAC,cAAc,EAAE,CAAA;YACtB,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,KAAK,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,KAAK,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,GAAG,CAAC,KAAK,CAAC,GAAG,KAAK,WAAW,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAA;QAC3H,CAAC;uBACc,CAAC,KAAmB,EAAE,EAAE;YACrC,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;gBAAE,OAAM;YAC9B,IAAI,CAAC,SAAS,GAAG,EAAE,CAAC,EAAE,KAAK,CAAC,OAAO,EAAE,KAAK,EAAE,IAAI,CAAC,qBAAqB,EAAE,CAAC,KAAK,EAAE,CAC/E;YAAC,KAAK,CAAC,aAA6B,CAAC,iBAAiB,CAAC,KAAK,CAAC,SAAS,CAAC,CAAA;QAC1E,CAAC;uBACc,CAAC,KAAmB,EAAE,EAAE,GAAG,IAAI,IAAI,CAAC,SAAS;YAAE,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,GAAG,IAAI,CAAC,SAAS,CAAC,CAAC,GAAG,KAAK,CAAC,OAAO,CAAC,CAAA,CAAC,CAAC;qBACvH,GAAG,EAAE,GAAG,IAAI,CAAC,SAAS,GAAG,SAAS,CAAA,CAAC,CAAC,mBAAmB,GAAG,EAAE,GAAG,IAAI,CAAC,SAAS,GAAG,SAAS,CAAA,CAAC,CAAC;;;UAGtG,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAA,yCAAyC,IAAI,CAAC,MAAM,CAAC,IAAI,UAAU,IAAI,CAAC,MAAM,CAAC,IAAI,WAAW,IAAI,CAAC,MAAM,4DAA4D,CAAC,CAAC,CAAC,EAAE;yFAC7G,IAAI,CAAC,KAAK,WAAW,IAAI,CAAC,OAAO;UAChH,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAA,4CAA4C,MAAA,IAAI,CAAC,MAAM,CAAC,OAAO,mCAAI,OAAO,CAAC,CAAC,CAAC,gBAAgB,CAAC;kBAC3G,MAAA,IAAI,CAAC,MAAM,CAAC,OAAO,mCAAI,OAAO,CAAC,CAAC,CAAC,gBAAgB,CAAC,WAAW,IAAI,CAAC,SAAS,yDAAyD,CAAC,CAAC,CAAC,EAAE;iEAC1F,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM;kBACxG,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,iBAAiB,IAAI,CAAC,QAAQ;mBACtF,GAAG,EAAE,GAAG,IAAI,CAAC,QAAQ,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAA,CAAC,CAAC,gCAAgC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,iBAAiB,CAAC,CAAC,CAAC,YAAY;iDAC1F,IAAI,CAAC,MAAM,CAAC,KAAK,UAAU,IAAI,CAAC,MAAM,CAAC,KAAK,WAAW,IAAI,CAAC,OAAO;;sDAE9D,IAAI;aAC7C,CAAA;IACX,CAAC;;AAlHM,sBAAM,GAAG,CAAC,eAAe,EAAE,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BpC,CAAC,AA5BW,CA4BX;AAC8B;IAA/B,QAAQ,CAAC,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC;6CAAqB;AACR;IAA3C,QAAQ,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;iDAAiB;AAEhB;IAA3C,QAAQ,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;kDAAkB;AAC5C;IAAhB,KAAK,EAAE;8CAAoB;AAEX;IAAhB,KAAK,EAAE;gDAAqC;AAhDlC,eAAe;IAD3B,aAAa,CAAC,mBAAmB,CAAC;GACtB,eAAe,CAgI3B","sourcesContent":["import '@material/web/icon/icon.js'\nimport { LitElement, css, html, type CSSResult, type TemplateResult } from 'lit'\nimport { customElement, property, state } from 'lit/decorators.js'\nimport { keyed } from 'lit/directives/keyed.js'\nimport { i18next } from '@operato/i18n'\nimport { DRAFT_UNKEPT } from '@operato/shell'\nimport { ifDefined } from 'lit/directives/if-defined.js'\nimport { ScrollbarStyles } from '@operato/styles'\n\n/** Products supply authorized contents and words; the shared dock owns presentation only. */\nexport interface Entity360View {\n /** Canonical subject + read scope, not a display name. Different revisions must have different keys. */\n key: string\n title: string\n context: string\n content: TemplateResult\n styles?: CSSResult[]\n labels: { close: string; expand: string; collapse: string; resize: string; back: string; refresh?: string }\n onClose(): void\n onBack?: () => void\n /**\n * Read the same subject again (ADR-0067 decision 8). The contents stay - what a section is writing stays with it;\n * each section asks its door anew. Without it the refresh button is not drawn.\n */\n onRefresh?: () => void\n}\n\n/**\n * A section tells the dock it holds an entry this browser cannot keep as a draft (ADR-0067 decision 8, ADR-0098\n * decision 5): `detail.unkept` true while so, false once saved, discarded or kept. Only then is leaving asked about.\n */\n\n/** Product CSS stays inside this boundary, never changes the dock's layout or controls. */\n@customElement('ox-entity360-content')\nexport class OxEntity360Content extends LitElement {\n @property({ attribute: false }) view?: Entity360View\n render() {\n return html`<style>${(this.view?.styles ?? []).map(style => style.cssText).join('\\n')}</style>${keyed(this.view?.key, this.view?.content)}`\n }\n}\n\n@customElement('ox-entity360-dock')\nexport class OxEntity360Dock extends LitElement {\n /** The sections that hold an entry no draft keeps. */\n private unkeptBy = new Set<EventTarget>()\n private onUnkept = (event: Event) => {\n const section = event.composedPath()[0]\n if ((event as CustomEvent<{ unkept: boolean }>).detail?.unkept) this.unkeptBy.add(section)\n else this.unkeptBy.delete(section)\n }\n /** Whether leaving this subject would lose an entry without a word - a draft keeps all the others. */\n get unkept(): boolean {\n for (const section of this.unkeptBy) if (!(section as Node).isConnected) this.unkeptBy.delete(section)\n return this.unkeptBy.size > 0\n }\n static styles = [ScrollbarStyles, css`\n :host { display: block; width: var(--entity360-width, 480px); max-width: 65vw; height: 100%; min-height: 0;\n color: var(--md-sys-color-on-surface, #222); background: var(--md-sys-color-surface, white);\n border-left: 1px solid var(--ui-line, #ccc); box-sizing: border-box; position: relative; }\n :host([expanded]) { position: fixed; inset: 0; width: 100vw; max-width: none; height: 100dvh; z-index: 901; }\n aside { height: 100%; display: flex; flex-direction: column; min-height: 0; }\n header { display: flex; align-items: flex-start; gap: 6px; padding: 12px 14px;\n border-bottom: 1px solid var(--ui-line, #ccc); flex: none; }\n .heading { flex: 1; min-width: 0; }\n h2 { font: 600 15px/1.4 var(--ui-font-family, sans-serif); margin: 0; overflow-wrap: anywhere; }\n h2:focus { outline: none; }\n p { font: 12px/1.4 var(--ui-font-family, sans-serif); margin: 4px 0 0; overflow-wrap: anywhere; color: var(--md-sys-color-on-surface-variant, #555); }\n button { font: 14px/1 sans-serif; width: 28px; height: 28px; padding: 0; display: grid; place-items: center;\n flex: none; cursor: pointer; border: none;\n border-radius: 6px; background: transparent; color: inherit; }\n p:empty { display: none; }\n button md-icon { --md-icon-size: 20px; }\n button:hover { background: var(--md-sys-color-surface-container-high, #eee); }\n button:focus-visible, [role=separator]:focus-visible { outline: 2px solid var(--md-sys-color-primary, #2865ad); outline-offset: -2px; }\n .body { overflow: auto; scrollbar-gutter: stable; min-height: 0; flex: 1; overscroll-behavior: contain; }\n .resize { position: absolute; left: -3px; top: 0; bottom: 0; width: 7px; cursor: col-resize; touch-action: none; z-index: 1; }\n :host([expanded]) .resize { display: none; }\n @media (max-width: 900px) {\n :host { position: fixed; inset: 0; width: 100vw; max-width: none; height: 100dvh; z-index: 901; }\n .resize, .expand { display: none; }\n }\n :host([contained]) { position: relative; inset: auto; width: 100%; max-width: none; height: 100%; z-index: auto; }\n @media (pointer: coarse) { button { width: 40px; height: 40px; } }\n `]\n @property({ attribute: false }) view?: Entity360View\n @property({ type: Boolean, reflect: true }) expanded = false\n /** The aside workspace owns layout and keyboard boundaries while a companion is visible. */\n @property({ type: Boolean, reflect: true }) contained = false\n @state() private width = 480\n private media = matchMedia('(max-width: 900px)')\n @state() private compact = this.media.matches\n private onMedia = () => { this.compact = this.media.matches }\n get fullscreen(): boolean { return this.expanded || this.compact }\n private dragStart?: { x: number; width: number }\n private onEscape = (event: KeyboardEvent) => {\n // A modal opened above the nonmodal dock owns Escape until it closes.\n if (this.contained || event.defaultPrevented || history.state?.overlay) return\n if (event.key === 'Escape') this.view?.onClose()\n if (event.key === 'Tab' && (this.expanded || this.compact)) {\n const walk = (root: ParentNode): HTMLElement[] => Array.from(root.querySelectorAll<HTMLElement>('*'))\n .flatMap(el => [el, ...(el.shadowRoot ? walk(el.shadowRoot) : [])])\n const controls = walk(this.renderRoot).filter(el => el.matches('button,a[href],input,select,textarea,summary,[tabindex]') &&\n el.getAttribute('tabindex') !== '-1' && !el.hasAttribute('disabled') && el.getClientRects().length && getComputedStyle(el).visibility !== 'hidden')\n const first = controls[0], last = controls[controls.length - 1]\n const target = event.composedPath()[0]\n if (!controls.includes(target as HTMLElement) || (event.shiftKey ? target === first : target === last)) {\n event.preventDefault()\n ;(event.shiftKey ? last : first)?.focus()\n }\n }\n }\n connectedCallback() {\n super.connectedCallback()\n document.addEventListener('keydown', this.onEscape)\n this.media.addEventListener('change', this.onMedia)\n this.addEventListener(DRAFT_UNKEPT, this.onUnkept)\n }\n disconnectedCallback() {\n document.removeEventListener('keydown', this.onEscape)\n this.media.removeEventListener('change', this.onMedia)\n this.removeEventListener(DRAFT_UNKEPT, this.onUnkept)\n super.disconnectedCallback()\n }\n firstUpdated() { this.focusHeading() }\n updated(changes: Map<PropertyKey, unknown>) {\n if (changes.has('expanded') || changes.has('compact') || changes.has('view'))\n this.dispatchEvent(new CustomEvent('entity360-presentation', { bubbles: true, composed: true }))\n const previous = changes.get('view') as Entity360View | undefined\n if (changes.has('view') && previous?.key !== this.view?.key) {\n this.renderRoot.querySelector('.body')?.scrollTo(0, 0)\n }\n }\n focusHeading() { this.renderRoot.querySelector<HTMLElement>('h2')?.focus({ preventScroll: true }) }\n private resize(width: number) {\n this.width = Math.max(320, Math.min(width, innerWidth * .65, 960))\n this.style.setProperty('--entity360-width', `${this.width}px`)\n }\n render() {\n const view = this.view\n if (!view) return html``\n const fullscreen = !this.contained && this.fullscreen\n return html`<aside role=${fullscreen ? 'dialog' : 'complementary'} aria-modal=${ifDefined(fullscreen ? 'true' : undefined)} aria-labelledby=\"entity360-title\">\n <div class=\"resize\" role=\"separator\" tabindex=\"0\" aria-orientation=\"vertical\"\n aria-label=${view.labels.resize} aria-valuemin=\"320\" aria-valuemax=${Math.min(innerWidth * .65, 960)} aria-valuenow=${this.width}\n @keydown=${(event: KeyboardEvent) => {\n if (!['ArrowLeft', 'ArrowRight', 'Home', 'End'].includes(event.key)) return\n event.preventDefault()\n this.resize(event.key === 'Home' ? 320 : event.key === 'End' ? 960 : this.width + (event.key === 'ArrowLeft' ? 32 : -32))\n }}\n @pointerdown=${(event: PointerEvent) => {\n if (event.button !== 0) return\n this.dragStart = { x: event.clientX, width: this.getBoundingClientRect().width }\n ;(event.currentTarget as HTMLElement).setPointerCapture(event.pointerId)\n }}\n @pointermove=${(event: PointerEvent) => { if (this.dragStart) this.resize(this.dragStart.width + this.dragStart.x - event.clientX) }}\n @pointerup=${() => { this.dragStart = undefined }} @pointercancel=${() => { this.dragStart = undefined }}\n ></div>\n <header>\n ${view.onBack ? html`<button data-action=\"back\" aria-label=${view.labels.back} title=${view.labels.back} @click=${view.onBack}><md-icon aria-hidden=\"true\">arrow_back</md-icon></button>` : ''}\n <div class=\"heading\" aria-live=\"polite\"><h2 id=\"entity360-title\" tabindex=\"-1\">${view.title}</h2><p>${view.context}</p></div>\n ${view.onRefresh ? html`<button data-action=\"refresh\" aria-label=${view.labels.refresh ?? i18next.t('button.refresh')}\n title=${view.labels.refresh ?? i18next.t('button.refresh')} @click=${view.onRefresh}><md-icon aria-hidden=\"true\">refresh</md-icon></button>` : ''}\n <button class=\"expand\" data-action=\"expand\" aria-label=${this.expanded ? view.labels.collapse : view.labels.expand}\n title=${this.expanded ? view.labels.collapse : view.labels.expand} aria-pressed=${this.expanded}\n @click=${() => { this.expanded = !this.expanded }}><md-icon aria-hidden=\"true\">${this.expanded ? 'fullscreen_exit' : 'fullscreen'}</md-icon></button>\n <button data-action=\"close\" aria-label=${view.labels.close} title=${view.labels.close} @click=${view.onClose}><md-icon aria-hidden=\"true\">close</md-icon></button>\n </header>\n <div class=\"body\"><ox-entity360-content .view=${view}></ox-entity360-content></div>\n </aside>`\n }\n}\n"]}
@@ -24,6 +24,8 @@ let NavBar = class NavBar extends LitElement {
24
24
  };
25
25
  })
26
26
  .filter(viewpart => viewpart.position == 'navbar' && (!this.fullbleed || viewpart.hovering));
27
+ // Floating mobile menus do not occupy a rail beside the page.
28
+ this.toggleAttribute('docked', navbars.some(navbar => navbar.show && !navbar.hovering));
27
29
  navbars = [
28
30
  ...navbars.filter(viewpart => viewpart.level == VIEWPART_LEVEL.TOPMOST),
29
31
  ...navbars.filter(viewpart => viewpart.level !== VIEWPART_LEVEL.TOPMOST)
@@ -95,10 +97,13 @@ NavBar.styles = [
95
97
  * 레일과 본문 배경이 팔레트에 따라 거의 같은 값이 될 수 있어(operato-board 다크에서
96
98
  * 대비 1.02) 경계가 사라졌다. 색차에만 기대지 않도록 경계선을 둔다.
97
99
  */
98
- border-right: var(--nav-bar-border, none);
99
100
  box-sizing: border-box;
100
101
  }
101
102
 
103
+ :host([docked]) {
104
+ border-right: var(--nav-bar-border, none);
105
+ }
106
+
102
107
  *[navbar] {
103
108
  display: flex;
104
109
  flex-direction: row;
@@ -1 +1 @@
1
- {"version":3,"file":"ox-nav-bar.js","sourceRoot":"","sources":["../../../src/layouts/ox-nav-bar.ts"],"names":[],"mappings":"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","sourcesContent":["import '@operato/popup/ox-floating-overlay.js'\nimport '../components/ox-resize-splitter.js'\n\nimport { css, html, LitElement } from 'lit'\nimport { ifDefined } from 'lit/directives/if-defined.js'\nimport { customElement, property } from 'lit/decorators.js'\nimport { LayoutController } from '@operato/shell'\n\nimport { ScrollbarStyles } from '@operato/styles'\n\nimport { Viewpart, VIEWPART_LEVEL } from '../actions/layout.js'\n\n@customElement('ox-nav-bar')\nclass NavBar extends LitElement {\n static styles = [\n ScrollbarStyles,\n css`\n :host {\n display: flex;\n flex-flow: row nowrap;\n align-items: stretch;\n\n position: relative;\n\n background-color: var(--nav-bar-background-color);\n /*\n * 레일과 본문 배경이 팔레트에 따라 거의 같은 값이 될 수 있어(operato-board 다크에서\n * 대비 1.02) 경계가 사라졌다. 색차에만 기대지 않도록 경계선을 둔다.\n */\n border-right: var(--nav-bar-border, none);\n box-sizing: border-box;\n }\n\n *[navbar] {\n display: flex;\n flex-direction: row;\n }\n `\n ]\n\n private _layout = new LayoutController(this)\n\n @property({ type: Boolean, attribute: 'fullbleed' }) fullbleed: boolean = false\n\n private _startWidth: number = 0\n\n render() {\n var viewparts = this._layout.viewparts\n var navbars = Object.keys(viewparts)\n .map(name => {\n return {\n name,\n ...viewparts[name]\n }\n })\n .filter(viewpart => viewpart.position == 'navbar' && (!this.fullbleed || viewpart.hovering))\n\n navbars = [\n ...navbars.filter(viewpart => viewpart.level == VIEWPART_LEVEL.TOPMOST),\n ...navbars.filter(viewpart => viewpart.level !== VIEWPART_LEVEL.TOPMOST)\n ]\n\n return html`\n ${navbars.map(navbar =>\n !navbar.show\n ? html``\n : navbar.hovering\n ? html`\n <ox-floating-overlay\n .backdrop=${navbar.backdrop}\n direction=\"right\"\n .hovering=${navbar.hovering}\n .name=${navbar.name}\n .title=${navbar.title}\n .help=${navbar.help}\n .size=${navbar.size}\n .closable=${navbar.closable}\n .templateProperties=${navbar.templateProperties}\n .historical=${true}\n .search=${navbar.search}\n .movable=${navbar.movable}\n .escapable=${navbar.escapable !== false}\n .filter=${navbar.filter}\n z-index=${ifDefined(navbar.zIndex)}\n >${navbar.template}</ox-floating-overlay\n >\n `\n : html`\n <div navbar>${navbar.template}</div>\n ${navbar.resizable\n ? html`\n <ox-resize-splitter\n @splitter-dragstart=${(e: CustomEvent) => this.resizeStart(e)}\n @splitter-drag=${(e: CustomEvent) => this.resizeDrag(e)}\n vertical\n ></ox-resize-splitter>\n `\n : html``}\n `\n )}\n `\n }\n\n resizeStart(e: CustomEvent) {\n this._startWidth = ((e.target as HTMLElement)?.previousElementSibling as HTMLElement).offsetWidth\n }\n\n resizeDrag(e: CustomEvent) {\n var delta = e.detail\n\n var x = ((e.target as HTMLElement)?.previousElementSibling as HTMLElement).children\n Array.from(x).forEach(ele => {\n ;(ele as HTMLElement).style.width = `${this._startWidth + delta.x}px`\n })\n }\n\n}\n"]}
1
+ {"version":3,"file":"ox-nav-bar.js","sourceRoot":"","sources":["../../../src/layouts/ox-nav-bar.ts"],"names":[],"mappings":"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","sourcesContent":["import '@operato/popup/ox-floating-overlay.js'\nimport '../components/ox-resize-splitter.js'\n\nimport { css, html, LitElement } from 'lit'\nimport { ifDefined } from 'lit/directives/if-defined.js'\nimport { customElement, property } from 'lit/decorators.js'\nimport { LayoutController } from '@operato/shell'\n\nimport { ScrollbarStyles } from '@operato/styles'\n\nimport { Viewpart, VIEWPART_LEVEL } from '../actions/layout.js'\n\n@customElement('ox-nav-bar')\nclass NavBar extends LitElement {\n static styles = [\n ScrollbarStyles,\n css`\n :host {\n display: flex;\n flex-flow: row nowrap;\n align-items: stretch;\n\n position: relative;\n\n background-color: var(--nav-bar-background-color);\n /*\n * 레일과 본문 배경이 팔레트에 따라 거의 같은 값이 될 수 있어(operato-board 다크에서\n * 대비 1.02) 경계가 사라졌다. 색차에만 기대지 않도록 경계선을 둔다.\n */\n box-sizing: border-box;\n }\n\n :host([docked]) {\n border-right: var(--nav-bar-border, none);\n }\n\n *[navbar] {\n display: flex;\n flex-direction: row;\n }\n `\n ]\n\n private _layout = new LayoutController(this)\n\n @property({ type: Boolean, attribute: 'fullbleed' }) fullbleed: boolean = false\n\n private _startWidth: number = 0\n\n render() {\n var viewparts = this._layout.viewparts\n var navbars = Object.keys(viewparts)\n .map(name => {\n return {\n name,\n ...viewparts[name]\n }\n })\n .filter(viewpart => viewpart.position == 'navbar' && (!this.fullbleed || viewpart.hovering))\n\n // Floating mobile menus do not occupy a rail beside the page.\n this.toggleAttribute('docked', navbars.some(navbar => navbar.show && !navbar.hovering))\n\n navbars = [\n ...navbars.filter(viewpart => viewpart.level == VIEWPART_LEVEL.TOPMOST),\n ...navbars.filter(viewpart => viewpart.level !== VIEWPART_LEVEL.TOPMOST)\n ]\n\n return html`\n ${navbars.map(navbar =>\n !navbar.show\n ? html``\n : navbar.hovering\n ? html`\n <ox-floating-overlay\n .backdrop=${navbar.backdrop}\n direction=\"right\"\n .hovering=${navbar.hovering}\n .name=${navbar.name}\n .title=${navbar.title}\n .help=${navbar.help}\n .size=${navbar.size}\n .closable=${navbar.closable}\n .templateProperties=${navbar.templateProperties}\n .historical=${true}\n .search=${navbar.search}\n .movable=${navbar.movable}\n .escapable=${navbar.escapable !== false}\n .filter=${navbar.filter}\n z-index=${ifDefined(navbar.zIndex)}\n >${navbar.template}</ox-floating-overlay\n >\n `\n : html`\n <div navbar>${navbar.template}</div>\n ${navbar.resizable\n ? html`\n <ox-resize-splitter\n @splitter-dragstart=${(e: CustomEvent) => this.resizeStart(e)}\n @splitter-drag=${(e: CustomEvent) => this.resizeDrag(e)}\n vertical\n ></ox-resize-splitter>\n `\n : html``}\n `\n )}\n `\n }\n\n resizeStart(e: CustomEvent) {\n this._startWidth = ((e.target as HTMLElement)?.previousElementSibling as HTMLElement).offsetWidth\n }\n\n resizeDrag(e: CustomEvent) {\n var delta = e.detail\n\n var x = ((e.target as HTMLElement)?.previousElementSibling as HTMLElement).children\n Array.from(x).forEach(ele => {\n ;(ele as HTMLElement).style.width = `${this._startWidth + delta.x}px`\n })\n }\n\n}\n"]}
@@ -0,0 +1 @@
1
+ export {};