@operato/layout 10.29.38 → 10.29.39

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.
@@ -1,7 +1,9 @@
1
1
  import { html } from 'lit';
2
2
  import { i18next } from '@operato/i18n';
3
+ import { addressController, writeAddress } from '@operato/utils/address.js';
3
4
  import { store, UPDATE_CONTEXT } from '@operato/shell';
4
5
  import { showEntity360, hideEntity360, canGoBackEntity360, isEntity360Shown, navigateEntity360 } from './entity360.js';
6
+ const returnFocusByAddress = new Map();
5
7
  const kinds = new Map();
6
8
  const kindOfTypename = new Map();
7
9
  const sections = new Map();
@@ -102,29 +104,32 @@ export function openEntity360Kind(kind, value, returnFocus) {
102
104
  const subject = { kind, id: String((_a = value === null || value === void 0 ? void 0 : value.id) !== null && _a !== void 0 ? _a : ''), value: value !== null && value !== void 0 ? value : {} };
103
105
  const address = addressOf(kind, subject.id);
104
106
  opened.set(address, subject.value);
107
+ if (returnFocus)
108
+ returnFocusByAddress.set(address, returnFocus);
105
109
  if (kind && subject.id) {
106
110
  const url = new URL(location.href);
107
111
  if (url.searchParams.get(FOCUS) !== address) {
108
112
  url.searchParams.set(FOCUS, address);
109
- navigateEntity360(url.pathname + url.search + url.hash);
113
+ void navigateEntity360(url.pathname + url.search + url.hash);
114
+ return;
110
115
  }
111
116
  }
112
117
  draw(subject, returnFocus);
113
118
  }
114
119
  /* Back and forward walk the address; the dock follows it and adds no step of its own. */
115
- window.addEventListener('popstate', () => {
120
+ addressController().subscribe(() => {
116
121
  var _a;
117
122
  const inAddress = subjectInAddress();
118
123
  if (inAddress && kinds.has(inAddress.kind)) {
119
124
  if ((shown === null || shown === void 0 ? void 0 : shown.kind) === inAddress.kind && shown.id === inAddress.id && isEntity360Shown(OWNER))
120
125
  return;
121
126
  const value = (_a = opened.get(addressOf(inAddress.kind, inAddress.id))) !== null && _a !== void 0 ? _a : { id: inAddress.id };
122
- draw({ ...inAddress, value });
127
+ draw({ ...inAddress, value }, returnFocusByAddress.get(addressOf(inAddress.kind, inAddress.id)));
123
128
  }
124
129
  else if (isEntity360Shown(OWNER)) {
125
130
  close(false);
126
131
  }
127
- });
132
+ }, false);
128
133
  /** Close the registry's 360 and take its step out of the address - for an app whose own state let go of it. */
129
134
  export function closeEntity360() {
130
135
  if (isEntity360Shown(OWNER))
@@ -151,15 +156,16 @@ function tell(subject) {
151
156
  listeners.forEach(listener => listener(subject));
152
157
  }
153
158
  function close(clearAddress) {
154
- hideEntity360(OWNER);
155
- shown = undefined;
156
- giveSubjectBack();
157
- tell(undefined);
158
159
  if (clearAddress && subjectInAddress()) {
159
160
  const url = new URL(location.href);
160
161
  url.searchParams.delete(FOCUS);
161
- history.replaceState(history.state, '', url.pathname + url.search + url.hash);
162
+ void writeAddress(url, { replace: true, state: history.state });
163
+ return;
162
164
  }
165
+ hideEntity360(OWNER);
166
+ shown = undefined;
167
+ giveSubjectBack();
168
+ tell(undefined);
163
169
  }
164
170
  function draw(subject, returnFocus) {
165
171
  var _a, _b;
@@ -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'\n\nimport { store, UPDATE_CONTEXT } from '@operato/shell'\n\nimport { showEntity360, hideEntity360, canGoBackEntity360, isEntity360Shown, navigateEntity360 } from './entity360.js'\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 /** 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/** 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): void {\n openEntity360Kind(entity360KindOf(value) ?? '', value, returnFocus)\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(kind: string, value: Record<string, any>, returnFocus?: () => void): void {\n const subject: Entity360Subject = { kind, id: String(value?.id ?? ''), value: value ?? {} }\n const address = addressOf(kind, subject.id)\n opened.set(address, subject.value)\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 navigateEntity360(url.pathname + url.search + url.hash)\n }\n }\n\n draw(subject, returnFocus)\n}\n\n/* Back and forward walk the address; the dock follows it and adds no step of its own. */\nwindow.addEventListener('popstate', () => {\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 })\n } else if (isEntity360Shown(OWNER)) {\n close(false)\n }\n})\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 * Draw the shown 360 again - its title and context are read anew. For a kind whose title comes from\n * what its section has just read.\n */\nexport function redrawEntity360(): void {\n if (shown && isEntity360Shown(OWNER)) draw(shown)\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 hideEntity360(OWNER)\n shown = undefined\n giveSubjectBack()\n tell(undefined)\n\n if (clearAddress && subjectInAddress()) {\n const url = new URL(location.href)\n url.searchParams.delete(FOCUS)\n history.replaceState(history.state, '', url.pathname + url.search + url.hash)\n }\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 },\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":"AAAA,OAAO,EAAE,IAAI,EAAuB,MAAM,KAAK,CAAA;AAC/C,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAA;AACvC,OAAO,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAA;AAE3E,OAAO,EAAE,KAAK,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAA;AAEtD,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAA;AAEtH,MAAM,oBAAoB,GAAG,IAAI,GAAG,EAAsB,CAAA;AAqD1D,MAAM,KAAK,GAAG,IAAI,GAAG,EAAoC,CAAA;AACzD,MAAM,cAAc,GAAG,IAAI,GAAG,EAAkB,CAAA;AAChD,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAiD,CAAA;AACzE,MAAM,QAAQ,GAAyB,EAAE,CAAA;AACzC,IAAI,UAAU,GAAG,CAAC,CAAA;AAElB,0EAA0E;AAC1E,MAAM,KAAK,GAAG,EAAE,CAAA;AAEhB;;;GAGG;AACH,MAAM,UAAU,qBAAqB,CAAC,WAAqC;;IACzE,MAAM,IAAI,GAAG,MAAM,CAAC,MAAA,WAAW,aAAX,WAAW,uBAAX,WAAW,CAAE,IAAI,mCAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAA;IAEnD,IAAI,CAAC,IAAI;QAAE,OAAO,KAAK,QAAQ,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC,CAAA;IAC9E,IAAI,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC;QAAE,OAAO,KAAK,QAAQ,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,gBAAgB,EAAE,CAAC,CAAA;IAClF,IAAI,OAAO,WAAW,CAAC,KAAK,KAAK,UAAU;QAAE,OAAO,KAAK,QAAQ,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,CAAC,CAAA;IAEpG,MAAM,KAAK,GAAG,CAAC,WAAW,CAAC,SAAS,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC,cAAc,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAA;IAC1F,IAAI,KAAK;QAAE,OAAO,KAAK,QAAQ,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,kBAAkB,EAAE,CAAC,CAAA;IAEvF,KAAK,CAAC,GAAG,CAAC,IAAI,EAAE,EAAE,GAAG,WAAW,EAAE,IAAI,EAAE,CAAC,CAAA;IACzC,KAAK,MAAM,QAAQ,IAAI,WAAW,CAAC,SAAS,IAAI,EAAE;QAAE,cAAc,CAAC,GAAG,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAA;AACxF,CAAC;AAED,wEAAwE;AACxE,MAAM,UAAU,wBAAwB,CAAC,IAAY,EAAE,OAAyB;;IAC9E,MAAM,IAAI,GAAG,MAAM,CAAC,MAAA,OAAO,aAAP,OAAO,uBAAP,OAAO,CAAE,IAAI,mCAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAA;IAE/C,IAAI,CAAC,IAAI;QAAE,OAAO,KAAK,QAAQ,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC,CAAA;IACjE,IAAI,OAAO,OAAO,CAAC,MAAM,KAAK,UAAU;QAAE,OAAO,KAAK,QAAQ,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,CAAC,CAAA;IAExG,MAAM,IAAI,GAAG,MAAA,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,mCAAI,EAAE,CAAA;IACrC,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,IAAI,KAAK,IAAI,CAAC;QAAE,OAAO,KAAK,QAAQ,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,gBAAgB,EAAE,CAAC,CAAA;IAE5G,IAAI,CAAC,IAAI,CAAC,EAAE,GAAG,OAAO,EAAE,IAAI,EAAE,EAAE,EAAE,UAAU,EAAE,EAAE,CAAC,CAAA;IACjD,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,eAAC,OAAA,CAAC,MAAA,CAAC,CAAC,KAAK,mCAAI,CAAC,CAAC,GAAG,CAAC,MAAA,CAAC,CAAC,KAAK,mCAAI,CAAC,CAAC,IAAI,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,EAAE,CAAA,EAAA,CAAC,CAAA;IACnE,QAAQ,CAAC,GAAG,CAAC,IAAI,EAAE,IAAI,CAAC,CAAA;AAC1B,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,eAAe,CAAC,KAAc;IAC5C,MAAM,QAAQ,GAAI,KAAa,aAAb,KAAK,uBAAL,KAAK,CAAU,UAAU,CAAA;IAC3C,OAAO,OAAO,QAAQ,KAAK,QAAQ,CAAC,CAAC,CAAC,cAAc,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,SAAS,CAAA;AAChF,CAAC;AAED,oFAAoF;AACpF,MAAM,UAAU,uBAAuB,CAAC,IAAY;IAClD,OAAO,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAA;AACxB,CAAC;AAED,6CAA6C;AAC7C,MAAM,UAAU,iBAAiB,CAAC,IAAY;;IAC5C,OAAO,CAAC,GAAG,CAAC,MAAA,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,mCAAI,EAAE,CAAC,CAAC,CAAA;AACxC,CAAC;AAED,sEAAsE;AACtE,MAAM,UAAU,mBAAmB;IACjC,OAAO,CAAC,GAAG,QAAQ,CAAC,CAAA;AACtB,CAAC;AAED,IAAI,KAAmC,CAAA;AACvC,iHAAiH;AACjH,MAAM,MAAM,GAAG,IAAI,GAAG,EAA+B,CAAA;AACrD,uFAAuF;AACvF,IAAI,WAA2E,CAAA;AAE/E,qGAAqG;AACrG,MAAM,KAAK,GAAG,GAAG,CAAA;AACjB,MAAM,SAAS,GAAG,CAAC,IAAY,EAAE,EAAU,EAAE,EAAE,CAAC,GAAG,IAAI,IAAI,EAAE,EAAE,CAAA;AAE/D,SAAS,gBAAgB;IACvB,MAAM,KAAK,GAAG,IAAI,eAAe,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,CAAA;IAC7D,MAAM,EAAE,GAAG,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;IAC1C,OAAO,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,KAAM,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE,KAAM,CAAC,KAAK,CAAC,EAAE,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,SAAS,CAAA;AACrF,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,gBAAgB,CAAC,KAA0B,EAAE,WAAwB;;IACnF,iBAAiB,CAAC,MAAA,eAAe,CAAC,KAAK,CAAC,mCAAI,EAAE,EAAE,KAAK,EAAE,WAAW,CAAC,CAAA;AACrE,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,iBAAiB,CAAC,IAAY,EAAE,KAA0B,EAAE,WAAwB;;IAClG,MAAM,OAAO,GAAqB,EAAE,IAAI,EAAE,EAAE,EAAE,MAAM,CAAC,MAAA,KAAK,aAAL,KAAK,uBAAL,KAAK,CAAE,EAAE,mCAAI,EAAE,CAAC,EAAE,KAAK,EAAE,KAAK,aAAL,KAAK,cAAL,KAAK,GAAI,EAAE,EAAE,CAAA;IAC3F,MAAM,OAAO,GAAG,SAAS,CAAC,IAAI,EAAE,OAAO,CAAC,EAAE,CAAC,CAAA;IAC3C,MAAM,CAAC,GAAG,CAAC,OAAO,EAAE,OAAO,CAAC,KAAK,CAAC,CAAA;IAClC,IAAI,WAAW;QAAE,oBAAoB,CAAC,GAAG,CAAC,OAAO,EAAE,WAAW,CAAC,CAAA;IAE/D,IAAI,IAAI,IAAI,OAAO,CAAC,EAAE,EAAE,CAAC;QACvB,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAA;QAClC,IAAI,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,OAAO,EAAE,CAAC;YAC5C,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,KAAK,EAAE,OAAO,CAAC,CAAA;YACpC,KAAK,iBAAiB,CAAC,GAAG,CAAC,QAAQ,GAAG,GAAG,CAAC,MAAM,GAAG,GAAG,CAAC,IAAI,CAAC,CAAA;YAC5D,OAAM;QACR,CAAC;IACH,CAAC;IAED,IAAI,CAAC,OAAO,EAAE,WAAW,CAAC,CAAA;AAC5B,CAAC;AAED,yFAAyF;AACzF,iBAAiB,EAAE,CAAC,SAAS,CAAC,GAAG,EAAE;;IACjC,MAAM,SAAS,GAAG,gBAAgB,EAAE,CAAA;IAEpC,IAAI,SAAS,IAAI,KAAK,CAAC,GAAG,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC;QAC3C,IAAI,CAAA,KAAK,aAAL,KAAK,uBAAL,KAAK,CAAE,IAAI,MAAK,SAAS,CAAC,IAAI,IAAI,KAAK,CAAC,EAAE,KAAK,SAAS,CAAC,EAAE,IAAI,gBAAgB,CAAC,KAAK,CAAC;YAAE,OAAM;QAClG,MAAM,KAAK,GAAG,MAAA,MAAM,CAAC,GAAG,CAAC,SAAS,CAAC,SAAS,CAAC,IAAI,EAAE,SAAS,CAAC,EAAE,CAAC,CAAC,mCAAI,EAAE,EAAE,EAAE,SAAS,CAAC,EAAE,EAAE,CAAA;QACzF,IAAI,CAAC,EAAE,GAAG,SAAS,EAAE,KAAK,EAAE,EAAE,oBAAoB,CAAC,GAAG,CAAC,SAAS,CAAC,SAAS,CAAC,IAAI,EAAE,SAAS,CAAC,EAAE,CAAC,CAAC,CAAC,CAAA;IAClG,CAAC;SAAM,IAAI,gBAAgB,CAAC,KAAK,CAAC,EAAE,CAAC;QACnC,KAAK,CAAC,KAAK,CAAC,CAAA;IACd,CAAC;AACH,CAAC,EAAE,KAAK,CAAC,CAAA;AAET,+GAA+G;AAC/G,MAAM,UAAU,cAAc;IAC5B,IAAI,gBAAgB,CAAC,KAAK,CAAC;QAAE,KAAK,CAAC,IAAI,CAAC,CAAA;AAC1C,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,eAAe;IAC7B,IAAI,KAAK,IAAI,gBAAgB,CAAC,KAAK,CAAC;QAAE,IAAI,CAAC,KAAK,CAAC,CAAA;AACnD,CAAC;AAGD,MAAM,SAAS,GAAG,IAAI,GAAG,EAAqB,CAAA;AAE9C;;;GAGG;AACH,MAAM,UAAU,iBAAiB,CAAC,QAA2B;IAC3D,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAA;IACvB,OAAO,GAAG,EAAE,CAAC,SAAS,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAA;AACzC,CAAC;AAED,SAAS,IAAI,CAAC,OAAqC;IACjD,SAAS,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAA;AAClD,CAAC;AAED,SAAS,KAAK,CAAC,YAAqB;IAClC,IAAI,YAAY,IAAI,gBAAgB,EAAE,EAAE,CAAC;QACvC,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAA;QAClC,GAAG,CAAC,YAAY,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;QAC9B,KAAK,YAAY,CAAC,GAAG,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,CAAC,CAAA;QAC/D,OAAM;IACR,CAAC;IACD,aAAa,CAAC,KAAK,CAAC,CAAA;IACpB,KAAK,GAAG,SAAS,CAAA;IACjB,eAAe,EAAE,CAAA;IACjB,IAAI,CAAC,SAAS,CAAC,CAAA;AACjB,CAAC;AAED,SAAS,IAAI,CAAC,OAAyB,EAAE,WAAwB;;IAC/D,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,OAAO,CAAA;IAC/B,MAAM,WAAW,GAAG,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAA;IACnC,MAAM,IAAI,GAAG,iBAAiB,CAAC,IAAI,CAAC,CAAA;IACpC,KAAK,GAAG,OAAO,CAAA;IAEf,MAAM,OAAO,GAAG,IAAI,CAAC,MAAM;QACzB,CAAC,CAAC,IAAI,CAAA,GAAG,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,EAAE;QACvD,CAAC,CAAC,IAAI,CAAA,mBAAmB,OAAO,CAAC,CAAC,CAAC,wBAAwB,EAAE,EAAE,YAAY,EAAE,oCAAoC,EAAE,CAAC,MAAM,CAAA;IAE5H,aAAa,CACX,KAAK,EACL;QACE,GAAG,EAAE,SAAS,CAAC,IAAI,EAAE,OAAO,CAAC,EAAE,CAAC;QAChC,KAAK,EAAE,WAAW,CAAC,CAAC,CAAC,WAAW,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,MAAA,MAAA,KAAK,aAAL,KAAK,uBAAL,KAAK,CAAE,IAAI,mCAAI,KAAK,aAAL,KAAK,uBAAL,KAAK,CAAE,EAAE,mCAAI,EAAE,CAAC;QACxF,OAAO,EAAE,CAAA,WAAW,aAAX,WAAW,uBAAX,WAAW,CAAE,OAAO,EAAC,CAAC,CAAC,WAAW,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,EAAE;QACjE,OAAO;QACP,MAAM,EAAE;YACN,KAAK,EAAE,OAAO,CAAC,CAAC,CAAC,cAAc,EAAE,EAAE,YAAY,EAAE,OAAO,EAAE,CAAC;YAC3D,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,eAAe,EAAE,EAAE,YAAY,EAAE,QAAQ,EAAE,CAAC;YAC9D,QAAQ,EAAE,OAAO,CAAC,CAAC,CAAC,iBAAiB,EAAE,EAAE,YAAY,EAAE,UAAU,EAAE,CAAC;YACpE,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,cAAc,EAAE,EAAE,YAAY,EAAE,QAAQ,EAAE,CAAC;YAC7D,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC,aAAa,EAAE,EAAE,YAAY,EAAE,MAAM,EAAE,CAAC;SACzD;QACD,OAAO,EAAE,GAAG,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC;QAC1B,MAAM,EAAE,kBAAkB,EAAE,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,SAAS;KAChE,EACD,WAAW,CACZ,CAAA;IAED,IAAI,WAAW,aAAX,WAAW,uBAAX,WAAW,CAAE,gBAAgB;QAAE,WAAW,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,EAAE,OAAO,CAAC,EAAE,EAAE,CAAC,CAAA;;QACzE,eAAe,EAAE,CAAA;IAEtB,IAAI,CAAC,OAAO,CAAC,CAAA;AACf,CAAC;AAED,SAAS,YAAY;;IACnB,OAAO,MAAA,MAAA,MAAC,KAAK,CAAC,QAAQ,EAAU,0CAAE,KAAK,0CAAE,OAAO,mCAAI,EAAE,CAAA;AACxD,CAAC;AAED,SAAS,WAAW,CAAC,IAAkC;IACrD,MAAM,OAAO,GAAG,YAAY,EAAE,CAAA;IAC9B,IAAI,CAAC,WAAW,IAAI,WAAW,CAAC,IAAI,KAAK,QAAQ,CAAC,QAAQ;QAAE,WAAW,GAAG,EAAE,IAAI,EAAE,QAAQ,CAAC,QAAQ,EAAE,OAAO,EAAE,OAAO,CAAC,OAAO,EAAE,CAAA;IAC/H,WAAW,CAAC,IAAI,GAAG,IAAI,CAAA;IACvB,KAAK,CAAC,QAAQ,CAAC,EAAE,IAAI,EAAE,cAAc,EAAE,OAAO,EAAE,EAAE,GAAG,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,EAAE,CAAC,CAAA;AAClF,CAAC;AAED,4FAA4F;AAC5F,SAAS,eAAe;IACtB,MAAM,KAAK,GAAG,WAAW,CAAA;IACzB,WAAW,GAAG,SAAS,CAAA;IACvB,IAAI,CAAC,KAAK,IAAI,KAAK,CAAC,IAAI,KAAK,QAAQ,CAAC,QAAQ;QAAE,OAAM;IAEtD,MAAM,OAAO,GAAG,YAAY,EAAE,CAAA;IAC9B,IAAI,OAAO,CAAC,OAAO,KAAK,KAAK,CAAC,IAAI;QAAE,KAAK,CAAC,QAAQ,CAAC,EAAE,IAAI,EAAE,cAAc,EAAE,OAAO,EAAE,EAAE,GAAG,OAAO,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,EAAE,CAAC,CAAA;AAC/H,CAAC","sourcesContent":["import { html, type TemplateResult } from 'lit'\nimport { i18next } from '@operato/i18n'\nimport { addressController, writeAddress } from '@operato/utils/address.js'\n\nimport { store, UPDATE_CONTEXT } from '@operato/shell'\n\nimport { showEntity360, hideEntity360, canGoBackEntity360, isEntity360Shown, 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 /** 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/** 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): void {\n openEntity360Kind(entity360KindOf(value) ?? '', value, returnFocus)\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(kind: string, value: Record<string, any>, returnFocus?: () => void): void {\n const subject: Entity360Subject = { kind, id: String(value?.id ?? ''), 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)\n return\n }\n }\n\n draw(subject, returnFocus)\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 * Draw the shown 360 again - its title and context are read anew. For a kind whose title comes from\n * what its section has just read.\n */\nexport function redrawEntity360(): void {\n if (shown && isEntity360Shown(OWNER)) draw(shown)\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 },\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"]}
@@ -5,5 +5,5 @@ export declare function showEntity360(owner: object, view: Entity360View, return
5
5
  export declare function isEntity360Shown(owner: object): boolean;
6
6
  export declare function hideEntity360(owner: object, restoreFocus?: boolean): void;
7
7
  /** Providers validate the canonical axis and read scope before calling this function. */
8
- export declare function navigateEntity360(address: string): void;
8
+ export declare function navigateEntity360(address: string): Promise<boolean>;
9
9
  export declare function canGoBackEntity360(): boolean;
@@ -1,6 +1,7 @@
1
1
  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
+ import { writeAddress } from '@operato/utils/address.js';
4
5
  const NAME = 'entity360';
5
6
  let current;
6
7
  // Cached pages may stay connected after navigation; their details must not leak into another task.
@@ -44,8 +45,8 @@ export function navigateEntity360(address) {
44
45
  if (url.origin !== location.origin)
45
46
  throw new Error('Entity 360 must stay within the application');
46
47
  if (url.href === location.href)
47
- return;
48
- history.pushState({ ...history.state, entity360Previous: location.href }, '', url);
48
+ return Promise.resolve(true);
49
+ return writeAddress(url, { state: { ...history.state, entity360Previous: location.href } });
49
50
  }
50
51
  export function canGoBackEntity360() {
51
52
  var _a;
@@ -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'\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\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/** Providers validate the canonical axis and read scope before calling this function. */\nexport function navigateEntity360(address: string): void {\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\n history.pushState({ ...history.state, entity360Previous: location.href }, '', url)\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'\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\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/** Providers validate the canonical axis and read scope before calling this function. */\nexport function navigateEntity360(address: string): 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 } })\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"]}
@@ -66,6 +66,6 @@ export declare enum TOOL_POSITION {
66
66
  export declare const UPDATE_VIEWPORT_WIDTH = "UPDATE_VIEWPORT_WIDTH";
67
67
  export declare const updateLayout: (wide?: boolean) => void;
68
68
  export declare const openOverlay: (name: string, options?: any, silent?: boolean) => void;
69
- export declare const closeOverlay: (name: string) => void;
69
+ export declare const closeOverlay: (name: string) => boolean;
70
70
  export declare const toggleOverlay: (name: string, options: any) => void;
71
71
  export declare const openPopup: <R = unknown>(template: TemplateResult, options?: PopupOptions) => import("@operato/popup").PopupHandle<R>;
@@ -1,6 +1,6 @@
1
1
  import { openPopup as openPopupWithResult, setOpenPopupImplementation } from '@operato/popup';
2
- import { claimHistoryBack } from '@operato/popup/implementation.js';
3
2
  import { store } from '@operato/shell';
3
+ import { addressController } from '@operato/utils/address.js';
4
4
  export const appendViewpart = ({ name, viewpart, position }) => {
5
5
  store.dispatch({
6
6
  type: APPEND_VIEWPART,
@@ -69,11 +69,46 @@ export const updateLayout = (wide) => {
69
69
  };
70
70
  /* overlay navigation */
71
71
  var overlaySequence = 0;
72
+ const pendingOverlays = new Map();
73
+ const overlays = [];
74
+ function syncModal() {
75
+ const modal = [...overlays]
76
+ .reverse()
77
+ .find(overlay => { var _a, _b, _c; return (_c = (_b = (_a = store.getState().layout) === null || _a === void 0 ? void 0 : _a.viewparts) === null || _b === void 0 ? void 0 : _b[overlay.name]) === null || _c === void 0 ? void 0 : _c.backdrop; });
78
+ addressController().setModal(modal ? { name: modal.name, close: () => dismissOverlays(0) } : undefined);
79
+ }
80
+ function dismissOverlays(from) {
81
+ for (const overlay of overlays.splice(from)) {
82
+ store.dispatch({ type: UPDATE_VIEWPART, name: overlay.name, override: { show: false } });
83
+ }
84
+ syncModal();
85
+ }
86
+ addressController().subscribe((entry, before) => {
87
+ var _a;
88
+ const firstGone = overlays.findIndex(overlay => {
89
+ const index = addressController().indexOf(overlay.key);
90
+ return index < 0 || index > entry.index;
91
+ });
92
+ if (firstGone >= 0)
93
+ dismissOverlays(firstGone);
94
+ if (entry.url.href !== before.url.href)
95
+ dismissOverlays(0);
96
+ const overlay = (_a = entry.state) === null || _a === void 0 ? void 0 : _a.overlay;
97
+ if (!overlay || !pendingOverlays.has(overlay.name))
98
+ return;
99
+ pendingOverlays.delete(overlay.name);
100
+ overlays.push({ ...overlay, key: entry.key, belowKey: before.key });
101
+ store.dispatch({ type: UPDATE_VIEWPART, name: overlay.name, override: { show: true } });
102
+ syncModal();
103
+ }, false);
104
+ document.addEventListener('keydown', event => {
105
+ const top = overlays[overlays.length - 1];
106
+ if ((top === null || top === void 0 ? void 0 : top.escapable) && (event.key === 'Escape' || event.key === 'Esc')) {
107
+ event.preventDefault();
108
+ closeOverlay(top.name);
109
+ }
110
+ });
72
111
  export const openOverlay = (name, options, silent) => {
73
- var beforeState = history.state;
74
- var beforeOverlay = beforeState ? beforeState.overlay : undefined;
75
- var beforeSequence = !beforeOverlay || beforeOverlay.sequence === undefined ? overlaySequence : beforeOverlay.sequence;
76
- var afterSequence = (overlaySequence = beforeSequence + 1);
77
112
  /* store의 layout의 내용을 변경한다. */
78
113
  if (!silent && options) {
79
114
  store.dispatch({
@@ -82,26 +117,36 @@ export const openOverlay = (name, options, silent) => {
82
117
  override: options
83
118
  });
84
119
  }
85
- /*
86
- * 현재 history.state를 확인하고, overlay의 이름이 같은
87
- * history에 추가하고 open 동작을 실행한다.
88
- */
89
- var afterState = Object.assign({}, beforeState || {}, {
90
- overlay: { name, sequence: afterSequence, escapable: (options === null || options === void 0 ? void 0 : options.escapable) !== false }
120
+ const request = new AbortController();
121
+ pendingOverlays.set(name, request);
122
+ void addressController()
123
+ .pushOverlay({
124
+ ...(history.state || {}),
125
+ overlay: { name, sequence: ++overlaySequence, escapable: (options === null || options === void 0 ? void 0 : options.escapable) !== false }
126
+ }, request.signal)
127
+ .then(accepted => {
128
+ if (accepted)
129
+ return;
130
+ pendingOverlays.delete(name);
131
+ store.dispatch({ type: UPDATE_VIEWPART, name, override: { show: false } });
132
+ document.dispatchEvent(new CustomEvent('overlay-closed', { detail: name }));
91
133
  });
92
- history.pushState(afterState, '', location.href);
93
- window.dispatchEvent(new CustomEvent('popstate', {
94
- detail: { state: afterState }
95
- }));
96
134
  };
97
135
  export const closeOverlay = (name) => {
98
- /*
99
- * 실제로 overlay를 close하는 작업은 window.onpopstate 핸들러에서 한다.
100
- */
101
- history.back();
136
+ const index = overlays.findIndex(overlay => overlay.name === name);
137
+ if (index < 0) {
138
+ const pending = pendingOverlays.get(name);
139
+ pending === null || pending === void 0 ? void 0 : pending.abort();
140
+ return !!pending;
141
+ }
142
+ const overlay = overlays[index];
143
+ dismissOverlays(index);
144
+ addressController().traverseTo(overlay.belowKey);
145
+ return true;
102
146
  };
103
147
  export const toggleOverlay = (name, options) => {
104
- var { name: beforeOverlayName } = (history.state && history.state.overlay) || {};
148
+ var _a;
149
+ var beforeOverlayName = (_a = overlays[overlays.length - 1]) === null || _a === void 0 ? void 0 : _a.name;
105
150
  if (beforeOverlayName == name) {
106
151
  closeOverlay(name);
107
152
  }
@@ -147,18 +192,10 @@ const openPopupInLayout = (template, options = {}) => {
147
192
  var popup = {
148
193
  name,
149
194
  close: () => {
150
- /* 현재 overlay state를 확인해서, 자신인 경우에 history.back() 한다. */
151
- var state = history.state;
152
- var overlay = (state || {}).overlay;
153
- /*
154
- * Not on top, or a back for this popup is already on its way: close nothing, and say so, so the
155
- * value of this request is not kept as the result.
156
- */
157
- if (!overlay || overlay.name != name || !claimHistoryBack(name)) {
195
+ var _a;
196
+ if (((_a = overlays[overlays.length - 1]) === null || _a === void 0 ? void 0 : _a.name) !== name && !pendingOverlays.has(name))
158
197
  return false;
159
- }
160
- history.back();
161
- return true;
198
+ return closeOverlay(name);
162
199
  },
163
200
  closed: false
164
201
  };
@@ -1 +1 @@
1
- {"version":3,"file":"layout.js","sourceRoot":"","sources":["../../../src/actions/layout.ts"],"names":[],"mappings":"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","sourcesContent":["import { TemplateResult } from 'lit'\n\nimport { openPopup as openPopupWithResult, PopupOptions, setOpenPopupImplementation } from '@operato/popup'\nimport { claimHistoryBack, PopupImplementationHandle } from '@operato/popup/implementation.js'\nimport { store } from '@operato/shell'\n\nexport type Viewpart = {\n hovering?: 'center' | 'edge' | 'next'\n show?: boolean\n size?: string\n title?: string\n help?: string\n level?: VIEWPART_LEVEL\n closable?: boolean\n search?: {\n value?: string\n handler?: (closure: any, value: string) => void\n placeholder?: string\n }\n filter?: {\n handler?: (closure: any) => void\n }\n backdrop?: boolean\n temporary?: boolean /* auto remove */\n persistent?: boolean /* application-wide dock remains available in fullbleed pages */\n resizable?: boolean\n movable?: boolean\n template?: TemplateResult\n position?: VIEWPART_POSITION\n templateProperties?: any\n zIndex?: number\n}\n\nexport type AppendViewpartAction = {\n name: string\n viewpart: Viewpart\n position: VIEWPART_POSITION\n}\n\nexport const appendViewpart = ({ name, viewpart, position }: AppendViewpartAction) => {\n store.dispatch({\n type: APPEND_VIEWPART,\n name,\n viewpart: {\n ...viewpart,\n show: viewpart.hovering && viewpart.show ? false : viewpart.show\n },\n position\n })\n\n if (viewpart.hovering && viewpart.show) {\n openOverlay(name)\n }\n}\n\nexport const removeViewpart = (name: string) => {\n store.dispatch({\n type: REMOVE_VIEWPART,\n name\n })\n}\n\nexport const updateViewpart = (name: string, override: any) => {\n store.dispatch({\n type: UPDATE_VIEWPART,\n name,\n override\n })\n}\n\nexport const APPEND_VIEWPART = 'APPEND_VIEWPART'\nexport const REMOVE_VIEWPART = 'REMOVE_VIEWPART'\nexport const UPDATE_VIEWPART = 'UPDATE_VIEWPART'\n\nexport enum VIEWPART_POSITION {\n HEADERBAR = 'headerbar',\n NAVBAR = 'navbar',\n ASIDEBAR = 'asidebar',\n FOOTERBAR = 'footerbar',\n PAGE_HEADERBAR = 'page-headerbar',\n PAGE_FOOTERBAR = 'page-footerbar'\n}\n\n/**\n * 각 뷰파트 스택의 순서를 정하는 속성이다.\n * 뷰파트의 level 속성에 정의되도록 한다.\n * 맨 위/아래/좌측/우측에 보여져야하는 뷰파트는 TOPMOST 속성을 갖도록 하고,\n * 쌓인 순서대로 보여지는 뷰파트는 NORMAL 속성을 갖도록 한다.\n * default 속성은 NORMAL이다.\n */\nexport enum VIEWPART_LEVEL {\n TOPMOST = 'TOPMOST',\n NORMAL = 'NORMAL'\n}\n\nexport enum TOOL_POSITION {\n FRONT_END = 'FRONT_END',\n FRONT = 'FRONT',\n CENTER = 'CENTER',\n REAR = 'REAR',\n REAR_END = 'REAR_END'\n}\n\nexport const UPDATE_VIEWPORT_WIDTH = 'UPDATE_VIEWPORT_WIDTH'\n\nexport const updateLayout = (wide?: boolean) => {\n store.dispatch({\n type: UPDATE_VIEWPORT_WIDTH,\n width: wide ? 'WIDE' : 'NARROW'\n })\n}\n\n/* overlay navigation */\nvar overlaySequence = 0\n\nexport const openOverlay = (name: string, options?: any, silent?: boolean) => {\n var beforeState = history.state\n var beforeOverlay = beforeState ? beforeState.overlay : undefined\n var beforeSequence = !beforeOverlay || beforeOverlay.sequence === undefined ? overlaySequence : beforeOverlay.sequence\n var afterSequence = (overlaySequence = beforeSequence + 1)\n\n /* store의 layout의 내용을 변경한다. */\n if (!silent && options) {\n store.dispatch({\n type: UPDATE_VIEWPART,\n name,\n override: options\n })\n }\n\n /*\n * 현재 history.state를 확인하고, overlay의 이름이 같은\n * history에 추가하고 open 동작을 실행한다.\n */\n var afterState = Object.assign({}, beforeState || {}, {\n overlay: { name, sequence: afterSequence, escapable: options?.escapable !== false }\n })\n\n history.pushState(afterState, '', location.href)\n\n window.dispatchEvent(\n new CustomEvent('popstate', {\n detail: { state: afterState }\n })\n )\n}\n\nexport const closeOverlay = (name: string) => {\n /*\n * 실제로 overlay를 close하는 작업은 window.onpopstate 핸들러에서 한다.\n */\n history.back()\n}\n\nexport const toggleOverlay = (name: string, options: any) => {\n var { name: beforeOverlayName } = (history.state && history.state.overlay) || {}\n\n if (beforeOverlayName == name) {\n closeOverlay(name)\n } else {\n openOverlay(name, options)\n }\n}\n\n/*\n * popup handling\n *\n * popup은 overlay의 특별한 형태이다.\n * popup은 open될 때, viewpart를 append 하며, close 될 때 viewpart를 remove 한다.\n * - name: '$popup-${popupSequence}'\n * - position: VIEWPART_POSITION_HEADERBAR\n * - hovering: 'center' | 'next' | 'edge'\n */\nvar popupSequence = 0\n\n/**\n * open popup in operato application environment\n *\n * @param {*} template html template to be rendered inside the popup\n * @param {PopupOptions} options\n * @returns the implementation handle. Callers get the full handle (with `result`) from `openPopup`.\n */\nconst openPopupInLayout = (template: TemplateResult, options: PopupOptions = {}): PopupImplementationHandle => {\n var name = `$popup-${popupSequence++}`\n\n appendViewpart({\n name,\n viewpart: {\n hovering: 'center',\n closable: true,\n movable: true,\n ...options,\n backdrop: true,\n show: false,\n temporary: true /* auto remove */,\n template\n },\n position: VIEWPART_POSITION.HEADERBAR\n })\n\n openOverlay(name, options, true)\n\n /* Typed, not cast: a cast would let a close() that forgets to answer compile. */\n var popup: PopupImplementationHandle = {\n name,\n close: () => {\n /* 현재 overlay state를 확인해서, 자신인 경우에 history.back() 한다. */\n var state = history.state\n var overlay = (state || {}).overlay\n\n /*\n * Not on top, or a back for this popup is already on its way: close nothing, and say so, so the\n * value of this request is not kept as the result.\n */\n if (!overlay || overlay.name != name || !claimHistoryBack(name)) {\n return false\n }\n\n history.back()\n return true\n },\n closed: false\n }\n\n document.addEventListener('overlay-closed', function listener(e) {\n if (name == (e as CustomEvent).detail) {\n popup.closed = true\n popup.onclosed && popup.onclosed()\n document.removeEventListener('overlay-closed', listener)\n }\n })\n\n return popup\n}\n\nsetOpenPopupImplementation(openPopupInLayout)\n\n/*\n * The popup package's openPopup adds `result` and `close(result)` on top of whichever implementation is\n * registered, so the handle is built in one place. Re-exported here because most callers import\n * openPopup from @operato/layout.\n */\nexport const openPopup = openPopupWithResult\n"]}
1
+ {"version":3,"file":"layout.js","sourceRoot":"","sources":["../../../src/actions/layout.ts"],"names":[],"mappings":"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","sourcesContent":["import { TemplateResult } from 'lit'\n\nimport { openPopup as openPopupWithResult, PopupOptions, setOpenPopupImplementation } from '@operato/popup'\nimport { PopupImplementationHandle } from '@operato/popup/implementation.js'\nimport { store } from '@operato/shell'\nimport { addressController } from '@operato/utils/address.js'\n\nexport type Viewpart = {\n hovering?: 'center' | 'edge' | 'next'\n show?: boolean\n size?: string\n title?: string\n help?: string\n level?: VIEWPART_LEVEL\n closable?: boolean\n search?: {\n value?: string\n handler?: (closure: any, value: string) => void\n placeholder?: string\n }\n filter?: {\n handler?: (closure: any) => void\n }\n backdrop?: boolean\n temporary?: boolean /* auto remove */\n persistent?: boolean /* application-wide dock remains available in fullbleed pages */\n resizable?: boolean\n movable?: boolean\n template?: TemplateResult\n position?: VIEWPART_POSITION\n templateProperties?: any\n zIndex?: number\n}\n\nexport type AppendViewpartAction = {\n name: string\n viewpart: Viewpart\n position: VIEWPART_POSITION\n}\n\nexport const appendViewpart = ({ name, viewpart, position }: AppendViewpartAction) => {\n store.dispatch({\n type: APPEND_VIEWPART,\n name,\n viewpart: {\n ...viewpart,\n show: viewpart.hovering && viewpart.show ? false : viewpart.show\n },\n position\n })\n\n if (viewpart.hovering && viewpart.show) {\n openOverlay(name)\n }\n}\n\nexport const removeViewpart = (name: string) => {\n store.dispatch({\n type: REMOVE_VIEWPART,\n name\n })\n}\n\nexport const updateViewpart = (name: string, override: any) => {\n store.dispatch({\n type: UPDATE_VIEWPART,\n name,\n override\n })\n}\n\nexport const APPEND_VIEWPART = 'APPEND_VIEWPART'\nexport const REMOVE_VIEWPART = 'REMOVE_VIEWPART'\nexport const UPDATE_VIEWPART = 'UPDATE_VIEWPART'\n\nexport enum VIEWPART_POSITION {\n HEADERBAR = 'headerbar',\n NAVBAR = 'navbar',\n ASIDEBAR = 'asidebar',\n FOOTERBAR = 'footerbar',\n PAGE_HEADERBAR = 'page-headerbar',\n PAGE_FOOTERBAR = 'page-footerbar'\n}\n\n/**\n * 각 뷰파트 스택의 순서를 정하는 속성이다.\n * 뷰파트의 level 속성에 정의되도록 한다.\n * 맨 위/아래/좌측/우측에 보여져야하는 뷰파트는 TOPMOST 속성을 갖도록 하고,\n * 쌓인 순서대로 보여지는 뷰파트는 NORMAL 속성을 갖도록 한다.\n * default 속성은 NORMAL이다.\n */\nexport enum VIEWPART_LEVEL {\n TOPMOST = 'TOPMOST',\n NORMAL = 'NORMAL'\n}\n\nexport enum TOOL_POSITION {\n FRONT_END = 'FRONT_END',\n FRONT = 'FRONT',\n CENTER = 'CENTER',\n REAR = 'REAR',\n REAR_END = 'REAR_END'\n}\n\nexport const UPDATE_VIEWPORT_WIDTH = 'UPDATE_VIEWPORT_WIDTH'\n\nexport const updateLayout = (wide?: boolean) => {\n store.dispatch({\n type: UPDATE_VIEWPORT_WIDTH,\n width: wide ? 'WIDE' : 'NARROW'\n })\n}\n\n/* overlay navigation */\nvar overlaySequence = 0\nconst pendingOverlays = new Map<string, AbortController>()\nconst overlays: { name: string; key: string; belowKey: string; escapable: boolean }[] = []\n\nfunction syncModal() {\n const modal = [...overlays]\n .reverse()\n .find(overlay => (store.getState() as any).layout?.viewparts?.[overlay.name]?.backdrop)\n addressController().setModal(modal ? { name: modal.name, close: () => dismissOverlays(0) } : undefined)\n}\n\nfunction dismissOverlays(from: number) {\n for (const overlay of overlays.splice(from)) {\n store.dispatch({ type: UPDATE_VIEWPART, name: overlay.name, override: { show: false } })\n }\n syncModal()\n}\n\naddressController().subscribe((entry, before) => {\n const firstGone = overlays.findIndex(overlay => {\n const index = addressController().indexOf(overlay.key)\n return index < 0 || index > entry.index\n })\n if (firstGone >= 0) dismissOverlays(firstGone)\n if (entry.url.href !== before.url.href) dismissOverlays(0)\n const overlay = (entry.state as any)?.overlay\n if (!overlay || !pendingOverlays.has(overlay.name)) return\n pendingOverlays.delete(overlay.name)\n overlays.push({ ...overlay, key: entry.key, belowKey: before.key })\n store.dispatch({ type: UPDATE_VIEWPART, name: overlay.name, override: { show: true } })\n syncModal()\n}, false)\n\ndocument.addEventListener('keydown', event => {\n const top = overlays[overlays.length - 1]\n if (top?.escapable && (event.key === 'Escape' || event.key === 'Esc')) {\n event.preventDefault()\n closeOverlay(top.name)\n }\n})\n\nexport const openOverlay = (name: string, options?: any, silent?: boolean) => {\n /* store의 layout의 내용을 변경한다. */\n if (!silent && options) {\n store.dispatch({\n type: UPDATE_VIEWPART,\n name,\n override: options\n })\n }\n\n const request = new AbortController()\n pendingOverlays.set(name, request)\n void addressController()\n .pushOverlay(\n {\n ...(history.state || {}),\n overlay: { name, sequence: ++overlaySequence, escapable: options?.escapable !== false }\n },\n request.signal\n )\n .then(accepted => {\n if (accepted) return\n pendingOverlays.delete(name)\n store.dispatch({ type: UPDATE_VIEWPART, name, override: { show: false } })\n document.dispatchEvent(new CustomEvent('overlay-closed', { detail: name }))\n })\n}\n\nexport const closeOverlay = (name: string) => {\n const index = overlays.findIndex(overlay => overlay.name === name)\n if (index < 0) {\n const pending = pendingOverlays.get(name)\n pending?.abort()\n return !!pending\n }\n const overlay = overlays[index]\n dismissOverlays(index)\n addressController().traverseTo(overlay.belowKey)\n return true\n}\n\nexport const toggleOverlay = (name: string, options: any) => {\n var beforeOverlayName = overlays[overlays.length - 1]?.name\n\n if (beforeOverlayName == name) {\n closeOverlay(name)\n } else {\n openOverlay(name, options)\n }\n}\n\n/*\n * popup handling\n *\n * popup은 overlay의 특별한 형태이다.\n * popup은 open될 때, viewpart를 append 하며, close 될 때 viewpart를 remove 한다.\n * - name: '$popup-${popupSequence}'\n * - position: VIEWPART_POSITION_HEADERBAR\n * - hovering: 'center' | 'next' | 'edge'\n */\nvar popupSequence = 0\n\n/**\n * open popup in operato application environment\n *\n * @param {*} template html template to be rendered inside the popup\n * @param {PopupOptions} options\n * @returns the implementation handle. Callers get the full handle (with `result`) from `openPopup`.\n */\nconst openPopupInLayout = (template: TemplateResult, options: PopupOptions = {}): PopupImplementationHandle => {\n var name = `$popup-${popupSequence++}`\n\n appendViewpart({\n name,\n viewpart: {\n hovering: 'center',\n closable: true,\n movable: true,\n ...options,\n backdrop: true,\n show: false,\n temporary: true /* auto remove */,\n template\n },\n position: VIEWPART_POSITION.HEADERBAR\n })\n\n openOverlay(name, options, true)\n\n /* Typed, not cast: a cast would let a close() that forgets to answer compile. */\n var popup: PopupImplementationHandle = {\n name,\n close: () => {\n if (overlays[overlays.length - 1]?.name !== name && !pendingOverlays.has(name)) return false\n return closeOverlay(name)\n },\n closed: false\n }\n\n document.addEventListener('overlay-closed', function listener(e) {\n if (name == (e as CustomEvent).detail) {\n popup.closed = true\n popup.onclosed && popup.onclosed()\n document.removeEventListener('overlay-closed', listener)\n }\n })\n\n return popup\n}\n\nsetOpenPopupImplementation(openPopupInLayout)\n\n/*\n * The popup package's openPopup adds `result` and `close(result)` on top of whichever implementation is\n * registered, so the handle is built in one place. Re-exported here because most callers import\n * openPopup from @operato/layout.\n */\nexport const openPopup = openPopupWithResult\n"]}
@@ -31,7 +31,8 @@ export declare class OxUrlTabs extends LitElement {
31
31
  /** Transitional: keep the key in a path segment after this page instead of the query. */
32
32
  base?: string;
33
33
  selected: string;
34
- private onPop;
34
+ private stopAddress?;
35
+ private stopTab?;
35
36
  connectedCallback(): void;
36
37
  disconnectedCallback(): void;
37
38
  updated(changed: Map<string, unknown>): void;
@@ -39,7 +40,7 @@ export declare class OxUrlTabs extends LitElement {
39
40
  keyInAddress(): string;
40
41
  private read;
41
42
  /** Choose a tab: the address follows, and the screen hears it. */
42
- select(key: string): void;
43
+ select(key: string): Promise<void>;
43
44
  private announce;
44
45
  private onKey;
45
46
  render(): import("lit-html").TemplateResult<1>;
@@ -2,6 +2,7 @@ import { __decorate } from "tslib";
2
2
  import { css, html, LitElement } from 'lit';
3
3
  import { customElement, property, state } from 'lit/decorators.js';
4
4
  import { navigate } from '@operato/shell';
5
+ import { addressController, writeAddress } from '@operato/utils/address.js';
5
6
  /**
6
7
  * Tabs whose choice lives in the address (ADR-0060 decision 8 addendum, the third frame).
7
8
  *
@@ -29,18 +30,29 @@ let OxUrlTabs = class OxUrlTabs extends LitElement {
29
30
  /** The query name the key is kept under. */
30
31
  this.param = 'tab';
31
32
  this.selected = '';
32
- this.onPop = () => this.read(true);
33
33
  }
34
34
  connectedCallback() {
35
35
  super.connectedCallback();
36
- window.addEventListener('popstate', this.onPop);
36
+ this.stopTab = addressController().registerTab(this.param);
37
+ const path = location.pathname;
38
+ this.stopAddress = addressController().subscribe(entry => {
39
+ if (entry.url.pathname === path || this.base)
40
+ this.read(true);
41
+ }, false);
37
42
  this.read(false);
38
43
  }
39
44
  disconnectedCallback() {
45
+ var _a, _b;
40
46
  super.disconnectedCallback();
41
- window.removeEventListener('popstate', this.onPop);
47
+ (_a = this.stopAddress) === null || _a === void 0 ? void 0 : _a.call(this);
48
+ (_b = this.stopTab) === null || _b === void 0 ? void 0 : _b.call(this);
42
49
  }
43
50
  updated(changed) {
51
+ var _a;
52
+ if (changed.has('param')) {
53
+ (_a = this.stopTab) === null || _a === void 0 ? void 0 : _a.call(this);
54
+ this.stopTab = addressController().registerTab(this.param);
55
+ }
44
56
  if (changed.has('tabs') || changed.has('base') || changed.has('param'))
45
57
  this.read(false);
46
58
  }
@@ -52,12 +64,12 @@ let OxUrlTabs = class OxUrlTabs extends LitElement {
52
64
  if (this.base) {
53
65
  const segments = location.pathname.split('/').filter(Boolean);
54
66
  const at = segments.lastIndexOf(this.base);
55
- named = at >= 0 ? (_a = segments[at + 1]) !== null && _a !== void 0 ? _a : null : null;
67
+ named = at >= 0 ? ((_a = segments[at + 1]) !== null && _a !== void 0 ? _a : null) : null;
56
68
  }
57
69
  else {
58
70
  named = new URLSearchParams(location.search).get(this.param);
59
71
  }
60
- return named && keys.includes(named) ? named : (_b = keys[0]) !== null && _b !== void 0 ? _b : '';
72
+ return named && keys.includes(named) ? named : ((_b = keys[0]) !== null && _b !== void 0 ? _b : '');
61
73
  }
62
74
  read(announce) {
63
75
  const key = this.keyInAddress();
@@ -68,13 +80,13 @@ let OxUrlTabs = class OxUrlTabs extends LitElement {
68
80
  this.announce();
69
81
  }
70
82
  /** Choose a tab: the address follows, and the screen hears it. */
71
- select(key) {
83
+ async select(key) {
72
84
  var _a;
73
85
  if (key === this.selected || !this.tabs.some(tab => tab.key === key))
74
86
  return;
75
87
  const first = (_a = this.tabs[0]) === null || _a === void 0 ? void 0 : _a.key;
76
88
  if (this.base) {
77
- navigate(key === first ? this.base : `${this.base}/${key}`);
89
+ await navigate(key === first ? this.base : `${this.base}/${key}`);
78
90
  }
79
91
  else {
80
92
  const url = new URL(location.href);
@@ -82,10 +94,8 @@ let OxUrlTabs = class OxUrlTabs extends LitElement {
82
94
  url.searchParams.delete(this.param);
83
95
  else
84
96
  url.searchParams.set(this.param, key);
85
- history.replaceState(history.state, '', url);
97
+ await writeAddress(url, { replace: true, state: history.state });
86
98
  }
87
- this.selected = key;
88
- this.announce();
89
99
  }
90
100
  announce() {
91
101
  this.dispatchEvent(new CustomEvent('tab-change', { detail: { key: this.selected }, bubbles: true, composed: true }));
@@ -1 +1 @@
1
- {"version":3,"file":"ox-url-tabs.js","sourceRoot":"","sources":["../../../src/components/ox-url-tabs.ts"],"names":[],"mappings":";AAAA,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,KAAK,CAAA;AAC3C,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAA;AAClE,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AAOzC;;;;;;;;;;;;;;;;;;;GAmBG;AAEI,IAAM,SAAS,GAAf,MAAM,SAAU,SAAQ,UAAU;IAAlC;;QA+B2B,SAAI,GAAe,EAAE,CAAA;QACrD,4CAA4C;QAChC,UAAK,GAAG,KAAK,CAAA;QAIhB,aAAQ,GAAG,EAAE,CAAA;QAEd,UAAK,GAAG,GAAG,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IAwFvC,CAAC;IAtFC,iBAAiB;QACf,KAAK,CAAC,iBAAiB,EAAE,CAAA;QACzB,MAAM,CAAC,gBAAgB,CAAC,UAAU,EAAE,IAAI,CAAC,KAAK,CAAC,CAAA;QAC/C,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;IAClB,CAAC;IAED,oBAAoB;QAClB,KAAK,CAAC,oBAAoB,EAAE,CAAA;QAC5B,MAAM,CAAC,mBAAmB,CAAC,UAAU,EAAE,IAAI,CAAC,KAAK,CAAC,CAAA;IACpD,CAAC;IAED,OAAO,CAAC,OAA6B;QACnC,IAAI,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC;YAAE,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;IAC1F,CAAC;IAED,8EAA8E;IAC9E,YAAY;;QACV,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;QAC1C,IAAI,KAAK,GAAkB,IAAI,CAAA;QAE/B,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC;YACd,MAAM,QAAQ,GAAG,QAAQ,CAAC,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAA;YAC7D,MAAM,EAAE,GAAG,QAAQ,CAAC,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;YAC1C,KAAK,GAAG,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,MAAA,QAAQ,CAAC,EAAE,GAAG,CAAC,CAAC,mCAAI,IAAI,CAAC,CAAC,CAAC,IAAI,CAAA;QACnD,CAAC;aAAM,CAAC;YACN,KAAK,GAAG,IAAI,eAAe,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;QAC9D,CAAC;QAED,OAAO,KAAK,IAAI,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAA,IAAI,CAAC,CAAC,CAAC,mCAAI,EAAE,CAAA;IAC9D,CAAC;IAEO,IAAI,CAAC,QAAiB;QAC5B,MAAM,GAAG,GAAG,IAAI,CAAC,YAAY,EAAE,CAAA;QAC/B,IAAI,GAAG,KAAK,IAAI,CAAC,QAAQ;YAAE,OAAM;QACjC,IAAI,CAAC,QAAQ,GAAG,GAAG,CAAA;QACnB,IAAI,QAAQ;YAAE,IAAI,CAAC,QAAQ,EAAE,CAAA;IAC/B,CAAC;IAED,kEAAkE;IAClE,MAAM,CAAC,GAAW;;QAChB,IAAI,GAAG,KAAK,IAAI,CAAC,QAAQ,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,GAAG,KAAK,GAAG,CAAC;YAAE,OAAM;QAC5E,MAAM,KAAK,GAAG,MAAA,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,0CAAE,GAAG,CAAA;QAE/B,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC;YACd,QAAQ,CAAC,GAAG,KAAK,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,IAAI,IAAI,GAAG,EAAE,CAAC,CAAA;QAC7D,CAAC;aAAM,CAAC;YACN,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAA;YAClC,IAAI,GAAG,KAAK,KAAK;gBAAE,GAAG,CAAC,YAAY,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;;gBACjD,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,EAAE,GAAG,CAAC,CAAA;YAC1C,OAAO,CAAC,YAAY,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,EAAE,GAAG,CAAC,CAAA;QAC9C,CAAC;QAED,IAAI,CAAC,QAAQ,GAAG,GAAG,CAAA;QACnB,IAAI,CAAC,QAAQ,EAAE,CAAA;IACjB,CAAC;IAEO,QAAQ;QACd,IAAI,CAAC,aAAa,CAAC,IAAI,WAAW,CAAC,YAAY,EAAE,EAAE,MAAM,EAAE,EAAE,GAAG,EAAE,IAAI,CAAC,QAAQ,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,CAAA;IACtH,CAAC;IAEO,KAAK,CAAC,KAAoB;QAChC,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;QAC1C,MAAM,EAAE,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAA;QACtC,MAAM,IAAI,GAAG,KAAK,CAAC,GAAG,KAAK,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,KAAK,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;QAChF,IAAI,CAAC,IAAI,IAAI,EAAE,GAAG,CAAC;YAAE,OAAM;QAC3B,KAAK,CAAC,cAAc,EAAE,CAAA;QACtB,MAAM,IAAI,GAAG,IAAI,CAAC,CAAC,EAAE,GAAG,IAAI,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC,CAAA;QAC1D,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAA;QACjB,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,GAAG,EAAE,WAAC,OAAA,MAAA,IAAI,CAAC,UAAU,CAAC,aAAa,CAAoB,cAAc,IAAI,IAAI,CAAC,0CAAE,KAAK,EAAE,CAAA,EAAA,CAAC,CAAA;IACnH,CAAC;IAED,MAAM;QACJ,OAAO,IAAI,CAAA,0DAA0D,IAAI,CAAC,KAAK;QAC3E,IAAI,CAAC,IAAI,CAAC,GAAG,CACb,GAAG,CAAC,EAAE,CAAC,IAAI,CAAA;;qBAEE,GAAG,CAAC,GAAG;0BACF,MAAM,CAAC,GAAG,CAAC,GAAG,KAAK,IAAI,CAAC,QAAQ,CAAC;qBACtC,GAAG,CAAC,GAAG,KAAK,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;mBACpC,GAAG,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC;;YAEjC,GAAG,CAAC,KAAK;kBACH,CACX;WACI,CAAA;IACT,CAAC;;AA7HM,gBAAM,GAAG,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BlB,AA5BY,CA4BZ;AAE+B;IAA/B,QAAQ,CAAC,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC;uCAAsB;AAEzC;IAAX,QAAQ,EAAE;wCAAc;AAEb;IAAX,QAAQ,EAAE;uCAAc;AAEhB;IAAR,KAAK,EAAE;2CAAc;AArCX,SAAS;IADrB,aAAa,CAAC,aAAa,CAAC;GAChB,SAAS,CA+HrB","sourcesContent":["import { css, html, LitElement } from 'lit'\nimport { customElement, property, state } from 'lit/decorators.js'\nimport { navigate } from '@operato/shell'\n\nexport interface OxUrlTab {\n key: string\n label: string\n}\n\n/**\n * Tabs whose choice lives in the address (ADR-0060 decision 8 addendum, the third frame).\n *\n * plant's quality and pending lists and its equipment screen keep their tab in the address, each its\n * own way; twin's business screen reads the tab from the address on entry and never writes it back, so\n * a copied link opens on the first tab. Here reading and writing always go together: open the link,\n * press back after opening a row, reload - the same tab.\n *\n * - `?tab=` (the `param` name) is where new screens keep it. The first tab is the default and is not\n * written, so a screen's plain address stays plain. Other query values are kept as they are.\n * The change replaces the address rather than adding a history step - back leaves the screen, it\n * does not walk through the tabs.\n * - `base` puts the key in a path segment instead (`<base>/<key>`), through the shell's navigate, the\n * way plant's three screens do. **Transitional** (architect ruling 2026-09-24): it is here so those\n * screens can take the frame without breaking the links people have; new screens use `?tab=`.\n * - `tab-change` (detail `{ key }`) fires when the tab changes - by a press, or by back / forward.\n * `selected` holds the current key, read from the address when the element connects.\n *\n * The look is the suite's tab strip (SurfaceStyles `.ui-tabs`): an underline under the chosen one.\n */\n@customElement('ox-url-tabs')\nexport class OxUrlTabs extends LitElement {\n static styles = css`\n :host {\n display: flex;\n gap: var(--spacing-large, 16px);\n border-bottom: 1px solid var(--ui-line, var(--md-sys-color-outline-variant));\n }\n button {\n border: none;\n border-bottom: 2px solid transparent;\n background: none;\n padding: var(--spacing-medium, 8px) 2px calc(var(--spacing-medium, 8px) - 2px);\n font: inherit;\n font-size: 14px;\n color: var(--ui-muted, var(--md-sys-color-on-surface-variant));\n cursor: pointer;\n }\n button:hover:not([aria-selected='true']) {\n color: var(--ui-ink, var(--md-sys-color-on-surface));\n }\n button[aria-selected='true'] {\n font-weight: 600;\n color: var(--ui-ink, var(--md-sys-color-on-surface));\n border-bottom-color: var(--ui-accent, var(--md-sys-color-primary));\n }\n button:focus-visible {\n outline: 2px solid var(--md-sys-color-primary);\n outline-offset: 2px;\n }\n `\n\n @property({ attribute: false }) tabs: OxUrlTab[] = []\n /** The query name the key is kept under. */\n @property() param = 'tab'\n /** Transitional: keep the key in a path segment after this page instead of the query. */\n @property() base?: string\n\n @state() selected = ''\n\n private onPop = () => this.read(true)\n\n connectedCallback() {\n super.connectedCallback()\n window.addEventListener('popstate', this.onPop)\n this.read(false)\n }\n\n disconnectedCallback() {\n super.disconnectedCallback()\n window.removeEventListener('popstate', this.onPop)\n }\n\n updated(changed: Map<string, unknown>) {\n if (changed.has('tabs') || changed.has('base') || changed.has('param')) this.read(false)\n }\n\n /** The key the address names, or the first tab when it names none of them. */\n keyInAddress(): string {\n const keys = this.tabs.map(tab => tab.key)\n let named: string | null = null\n\n if (this.base) {\n const segments = location.pathname.split('/').filter(Boolean)\n const at = segments.lastIndexOf(this.base)\n named = at >= 0 ? segments[at + 1] ?? null : null\n } else {\n named = new URLSearchParams(location.search).get(this.param)\n }\n\n return named && keys.includes(named) ? named : keys[0] ?? ''\n }\n\n private read(announce: boolean) {\n const key = this.keyInAddress()\n if (key === this.selected) return\n this.selected = key\n if (announce) this.announce()\n }\n\n /** Choose a tab: the address follows, and the screen hears it. */\n select(key: string) {\n if (key === this.selected || !this.tabs.some(tab => tab.key === key)) return\n const first = this.tabs[0]?.key\n\n if (this.base) {\n navigate(key === first ? this.base : `${this.base}/${key}`)\n } else {\n const url = new URL(location.href)\n if (key === first) url.searchParams.delete(this.param)\n else url.searchParams.set(this.param, key)\n history.replaceState(history.state, '', url)\n }\n\n this.selected = key\n this.announce()\n }\n\n private announce() {\n this.dispatchEvent(new CustomEvent('tab-change', { detail: { key: this.selected }, bubbles: true, composed: true }))\n }\n\n private onKey(event: KeyboardEvent) {\n const keys = this.tabs.map(tab => tab.key)\n const at = keys.indexOf(this.selected)\n const step = event.key === 'ArrowRight' ? 1 : event.key === 'ArrowLeft' ? -1 : 0\n if (!step || at < 0) return\n event.preventDefault()\n const next = keys[(at + step + keys.length) % keys.length]\n this.select(next)\n this.updateComplete.then(() => this.renderRoot.querySelector<HTMLButtonElement>(`[data-key=\"${next}\"]`)?.focus())\n }\n\n render() {\n return html`<div role=\"tablist\" style=\"display: contents\" @keydown=${this.onKey}>\n ${this.tabs.map(\n tab => html`<button\n role=\"tab\"\n data-key=${tab.key}\n aria-selected=${String(tab.key === this.selected)}\n tabindex=${tab.key === this.selected ? 0 : -1}\n @click=${() => this.select(tab.key)}\n >\n ${tab.label}\n </button>`\n )}\n </div>`\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'ox-url-tabs': OxUrlTabs\n }\n}\n"]}
1
+ {"version":3,"file":"ox-url-tabs.js","sourceRoot":"","sources":["../../../src/components/ox-url-tabs.ts"],"names":[],"mappings":"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","sourcesContent":["import { css, html, LitElement } from 'lit'\nimport { customElement, property, state } from 'lit/decorators.js'\nimport { navigate } from '@operato/shell'\nimport { addressController, writeAddress } from '@operato/utils/address.js'\n\nexport interface OxUrlTab {\n key: string\n label: string\n}\n\n/**\n * Tabs whose choice lives in the address (ADR-0060 decision 8 addendum, the third frame).\n *\n * plant's quality and pending lists and its equipment screen keep their tab in the address, each its\n * own way; twin's business screen reads the tab from the address on entry and never writes it back, so\n * a copied link opens on the first tab. Here reading and writing always go together: open the link,\n * press back after opening a row, reload - the same tab.\n *\n * - `?tab=` (the `param` name) is where new screens keep it. The first tab is the default and is not\n * written, so a screen's plain address stays plain. Other query values are kept as they are.\n * The change replaces the address rather than adding a history step - back leaves the screen, it\n * does not walk through the tabs.\n * - `base` puts the key in a path segment instead (`<base>/<key>`), through the shell's navigate, the\n * way plant's three screens do. **Transitional** (architect ruling 2026-09-24): it is here so those\n * screens can take the frame without breaking the links people have; new screens use `?tab=`.\n * - `tab-change` (detail `{ key }`) fires when the tab changes - by a press, or by back / forward.\n * `selected` holds the current key, read from the address when the element connects.\n *\n * The look is the suite's tab strip (SurfaceStyles `.ui-tabs`): an underline under the chosen one.\n */\n@customElement('ox-url-tabs')\nexport class OxUrlTabs extends LitElement {\n static styles = css`\n :host {\n display: flex;\n gap: var(--spacing-large, 16px);\n border-bottom: 1px solid var(--ui-line, var(--md-sys-color-outline-variant));\n }\n button {\n border: none;\n border-bottom: 2px solid transparent;\n background: none;\n padding: var(--spacing-medium, 8px) 2px calc(var(--spacing-medium, 8px) - 2px);\n font: inherit;\n font-size: 14px;\n color: var(--ui-muted, var(--md-sys-color-on-surface-variant));\n cursor: pointer;\n }\n button:hover:not([aria-selected='true']) {\n color: var(--ui-ink, var(--md-sys-color-on-surface));\n }\n button[aria-selected='true'] {\n font-weight: 600;\n color: var(--ui-ink, var(--md-sys-color-on-surface));\n border-bottom-color: var(--ui-accent, var(--md-sys-color-primary));\n }\n button:focus-visible {\n outline: 2px solid var(--md-sys-color-primary);\n outline-offset: 2px;\n }\n `\n\n @property({ attribute: false }) tabs: OxUrlTab[] = []\n /** The query name the key is kept under. */\n @property() param = 'tab'\n /** Transitional: keep the key in a path segment after this page instead of the query. */\n @property() base?: string\n\n @state() selected = ''\n\n private stopAddress?: () => void\n private stopTab?: () => void\n\n connectedCallback() {\n super.connectedCallback()\n this.stopTab = addressController().registerTab(this.param)\n const path = location.pathname\n this.stopAddress = addressController().subscribe(entry => {\n if (entry.url.pathname === path || this.base) this.read(true)\n }, false)\n this.read(false)\n }\n\n disconnectedCallback() {\n super.disconnectedCallback()\n this.stopAddress?.()\n this.stopTab?.()\n }\n\n updated(changed: Map<string, unknown>) {\n if (changed.has('param')) {\n this.stopTab?.()\n this.stopTab = addressController().registerTab(this.param)\n }\n if (changed.has('tabs') || changed.has('base') || changed.has('param')) this.read(false)\n }\n\n /** The key the address names, or the first tab when it names none of them. */\n keyInAddress(): string {\n const keys = this.tabs.map(tab => tab.key)\n let named: string | null = null\n\n if (this.base) {\n const segments = location.pathname.split('/').filter(Boolean)\n const at = segments.lastIndexOf(this.base)\n named = at >= 0 ? (segments[at + 1] ?? null) : null\n } else {\n named = new URLSearchParams(location.search).get(this.param)\n }\n\n return named && keys.includes(named) ? named : (keys[0] ?? '')\n }\n\n private read(announce: boolean) {\n const key = this.keyInAddress()\n if (key === this.selected) return\n this.selected = key\n if (announce) this.announce()\n }\n\n /** Choose a tab: the address follows, and the screen hears it. */\n async select(key: string) {\n if (key === this.selected || !this.tabs.some(tab => tab.key === key)) return\n const first = this.tabs[0]?.key\n\n if (this.base) {\n await navigate(key === first ? this.base : `${this.base}/${key}`)\n } else {\n const url = new URL(location.href)\n if (key === first) url.searchParams.delete(this.param)\n else url.searchParams.set(this.param, key)\n await writeAddress(url, { replace: true, state: history.state })\n }\n }\n\n private announce() {\n this.dispatchEvent(new CustomEvent('tab-change', { detail: { key: this.selected }, bubbles: true, composed: true }))\n }\n\n private onKey(event: KeyboardEvent) {\n const keys = this.tabs.map(tab => tab.key)\n const at = keys.indexOf(this.selected)\n const step = event.key === 'ArrowRight' ? 1 : event.key === 'ArrowLeft' ? -1 : 0\n if (!step || at < 0) return\n event.preventDefault()\n const next = keys[(at + step + keys.length) % keys.length]\n this.select(next)\n this.updateComplete.then(() => this.renderRoot.querySelector<HTMLButtonElement>(`[data-key=\"${next}\"]`)?.focus())\n }\n\n render() {\n return html`<div role=\"tablist\" style=\"display: contents\" @keydown=${this.onKey}>\n ${this.tabs.map(\n tab => html`<button\n role=\"tab\"\n data-key=${tab.key}\n aria-selected=${String(tab.key === this.selected)}\n tabindex=${tab.key === this.selected ? 0 : -1}\n @click=${() => this.select(tab.key)}\n >\n ${tab.label}\n </button>`\n )}\n </div>`\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'ox-url-tabs': OxUrlTabs\n }\n}\n"]}