@operato/layout 10.29.24 → 10.29.25

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,2 +1,8 @@
1
1
  import { type TemplateResult } from 'lit';
2
2
  export declare function entity360Name(value: Record<string, any> | null | undefined, text?: string): TemplateResult | string;
3
+ /**
4
+ * An identifier as the screen draws it when the screen knows its kind but holds no typed value - an
5
+ * id inside a row (a location in a stock line), or a relation inside another 360. The same link as
6
+ * entity360Name; plain text for a kind nobody registered, or an empty id.
7
+ */
8
+ export declare function entity360Link(kind: string, id: string | null | undefined, text?: string): TemplateResult | string;
@@ -1,5 +1,5 @@
1
1
  import { html } from 'lit';
2
- import { entity360KindOf, openEntity360For } from './entity360-registry.js';
2
+ import { entity360KindOf, entity360KindRegistered, openEntity360Kind } from './entity360-registry.js';
3
3
  /**
4
4
  * An entity's name as the screen draws it: a link to its 360 when its kind is registered, the plain
5
5
  * text otherwise (ADR-0067 decision 1 - an entity's name opens its 360; ADR-0071 - no dead links).
@@ -21,8 +21,22 @@ function updateDecoration(event, hovered) {
21
21
  export function entity360Name(value, text) {
22
22
  var _a;
23
23
  const label = (_a = text !== null && text !== void 0 ? text : value === null || value === void 0 ? void 0 : value.name) !== null && _a !== void 0 ? _a : '';
24
- if (!value || !entity360KindOf(value))
24
+ const kind = value ? entity360KindOf(value) : undefined;
25
+ return kind ? link(kind, value, label) : label;
26
+ }
27
+ /**
28
+ * An identifier as the screen draws it when the screen knows its kind but holds no typed value - an
29
+ * id inside a row (a location in a stock line), or a relation inside another 360. The same link as
30
+ * entity360Name; plain text for a kind nobody registered, or an empty id.
31
+ */
32
+ export function entity360Link(kind, id, text) {
33
+ var _a;
34
+ const label = (_a = text !== null && text !== void 0 ? text : id) !== null && _a !== void 0 ? _a : '';
35
+ if (!id || !entity360KindRegistered(kind))
25
36
  return label;
37
+ return link(kind, { id, name: label }, label);
38
+ }
39
+ function link(kind, value, label) {
26
40
  return html `<a
27
41
  href="#"
28
42
  data-entity360
@@ -40,7 +54,7 @@ export function entity360Name(value, text) {
40
54
  event.preventDefault();
41
55
  event.stopPropagation();
42
56
  const opener = event.currentTarget;
43
- openEntity360For(value, () => { if (opener.isConnected)
57
+ openEntity360Kind(kind, value, () => { if (opener.isConnected)
44
58
  opener.focus(); });
45
59
  }}
46
60
  >${label}</a
@@ -1 +1 @@
1
- {"version":3,"file":"entity360-name.js","sourceRoot":"","sources":["../../../src/actions/entity360-name.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAuB,MAAM,KAAK,CAAA;AAE/C,OAAO,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAA;AAE3E;;;;;;;;;;;GAWG;AACH,MAAM,SAAS,GAAG,mDAAmD,CAAA;AACrE,MAAM,WAAW,GAAG,mDAAmD,CAAA;AAEvE,SAAS,gBAAgB,CAAC,KAAY,EAAE,OAAgB;IACtD,MAAM,IAAI,GAAG,KAAK,CAAC,aAA4B,CAAA;IAC/C,IAAI,CAAC,KAAK,CAAC,mBAAmB,GAAG,OAAO,IAAI,IAAI,CAAC,OAAO,CAAC,wBAAwB,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,SAAS,CAAA;AAC9G,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,KAA6C,EAAE,IAAa;;IACxF,MAAM,KAAK,GAAG,MAAA,IAAI,aAAJ,IAAI,cAAJ,IAAI,GAAI,KAAK,aAAL,KAAK,uBAAL,KAAK,CAAE,IAAI,mCAAI,EAAE,CAAA;IAEvC,IAAI,CAAC,KAAK,IAAI,CAAC,eAAe,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAA;IAEnD,OAAO,IAAI,CAAA;;;6JAGgJ,SAAS;oBAClJ,CAAC,KAAY,EAAE,EAAE,CAAC,gBAAgB,CAAC,KAAK,EAAE,IAAI,CAAC;oBAC/C,CAAC,KAAY,EAAE,EAAE,CAAC,gBAAgB,CAAC,KAAK,EAAE,KAAK,CAAC;aACvD,CAAC,KAAY,EAAE,EAAE,CAAC,gBAAgB,CAAC,KAAK,EAAE,KAAK,CAAC;YACjD,CAAC,KAAY,EAAE,EAAE,CAAC,gBAAgB,CAAC,KAAK,EAAE,KAAK,CAAC;mBACzC,CAAC,KAAY,EAAE,EAAE,CAAC,KAAK,CAAC,eAAe,EAAE;eAC7C,CAAC,KAAoB,EAAE,EAAE;QAClC,IAAI,KAAK,CAAC,GAAG,KAAK,OAAO;YAAE,KAAK,CAAC,eAAe,EAAE,CAAA;IACpD,CAAC;aACQ,CAAC,KAAY,EAAE,EAAE;QACxB,KAAK,CAAC,cAAc,EAAE,CAAA;QACtB,KAAK,CAAC,eAAe,EAAE,CAAA;QACvB,MAAM,MAAM,GAAG,KAAK,CAAC,aAA4B,CAAA;QACjD,gBAAgB,CAAC,KAAK,EAAE,GAAG,EAAE,GAAG,IAAI,MAAM,CAAC,WAAW;YAAE,MAAM,CAAC,KAAK,EAAE,CAAA,CAAC,CAAC,CAAC,CAAA;IAC3E,CAAC;OACE,KAAK;IACR,CAAA;AACJ,CAAC","sourcesContent":["import { html, type TemplateResult } from 'lit'\n\nimport { entity360KindOf, openEntity360For } from './entity360-registry.js'\n\n/**\n * An entity's name as the screen draws it: a link to its 360 when its kind is registered, the plain\n * text otherwise (ADR-0067 decision 1 - an entity's name opens its 360; ADR-0071 - no dead links).\n *\n * One markup for every place a name is drawn - a grid cell, a ribbon's 「담당자」, a detail's\n * 「올린 사람」 - with the same interaction everywhere.\n *\n * The link keeps the body colour and carries a thin, faint underline set off from the text, so a\n * column of names stays calm; the line firms up under the pointer or keyboard focus. Keep this in\n * the helper as it also renders inside grid and section shadow roots. A click does not travel on:\n * inside a grid row it would start editing or select the row.\n */\nconst LINE_REST = 'color-mix(in srgb, currentColor 30%, transparent)'\nconst LINE_ACTIVE = 'color-mix(in srgb, currentColor 65%, transparent)'\n\nfunction updateDecoration(event: Event, hovered: boolean) {\n const link = event.currentTarget as HTMLElement\n link.style.textDecorationColor = hovered || link.matches(':hover, :focus-visible') ? LINE_ACTIVE : LINE_REST\n}\n\nexport function entity360Name(value: Record<string, any> | null | undefined, text?: string): TemplateResult | string {\n const label = text ?? value?.name ?? ''\n\n if (!value || !entity360KindOf(value)) return label\n\n return html`<a\n href=\"#\"\n data-entity360\n style=\"color: inherit; cursor: pointer; text-decoration: underline; text-decoration-thickness: 1px; text-underline-offset: 3px; text-decoration-color: ${LINE_REST}; transition: text-decoration-color 0.12s\"\n @pointerenter=${(event: Event) => updateDecoration(event, true)}\n @pointerleave=${(event: Event) => updateDecoration(event, false)}\n @focus=${(event: Event) => updateDecoration(event, false)}\n @blur=${(event: Event) => updateDecoration(event, false)}\n @pointerdown=${(event: Event) => event.stopPropagation()}\n @keydown=${(event: KeyboardEvent) => {\n if (event.key === 'Enter') event.stopPropagation()\n }}\n @click=${(event: Event) => {\n event.preventDefault()\n event.stopPropagation()\n const opener = event.currentTarget as HTMLElement\n openEntity360For(value, () => { if (opener.isConnected) opener.focus() })\n }}\n >${label}</a\n >`\n}\n"]}
1
+ {"version":3,"file":"entity360-name.js","sourceRoot":"","sources":["../../../src/actions/entity360-name.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAuB,MAAM,KAAK,CAAA;AAE/C,OAAO,EAAE,eAAe,EAAE,uBAAuB,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAA;AAErG;;;;;;;;;;;GAWG;AACH,MAAM,SAAS,GAAG,mDAAmD,CAAA;AACrE,MAAM,WAAW,GAAG,mDAAmD,CAAA;AAEvE,SAAS,gBAAgB,CAAC,KAAY,EAAE,OAAgB;IACtD,MAAM,IAAI,GAAG,KAAK,CAAC,aAA4B,CAAA;IAC/C,IAAI,CAAC,KAAK,CAAC,mBAAmB,GAAG,OAAO,IAAI,IAAI,CAAC,OAAO,CAAC,wBAAwB,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,SAAS,CAAA;AAC9G,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,KAA6C,EAAE,IAAa;;IACxF,MAAM,KAAK,GAAG,MAAA,IAAI,aAAJ,IAAI,cAAJ,IAAI,GAAI,KAAK,aAAL,KAAK,uBAAL,KAAK,CAAE,IAAI,mCAAI,EAAE,CAAA;IACvC,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,eAAe,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,SAAS,CAAA;IAEvD,OAAO,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,EAAE,KAAM,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAA;AACjD,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,aAAa,CAAC,IAAY,EAAE,EAA6B,EAAE,IAAa;;IACtF,MAAM,KAAK,GAAG,MAAA,IAAI,aAAJ,IAAI,cAAJ,IAAI,GAAI,EAAE,mCAAI,EAAE,CAAA;IAC9B,IAAI,CAAC,EAAE,IAAI,CAAC,uBAAuB,CAAC,IAAI,CAAC;QAAE,OAAO,KAAK,CAAA;IAEvD,OAAO,IAAI,CAAC,IAAI,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,KAAK,CAAC,CAAA;AAC/C,CAAC;AAED,SAAS,IAAI,CAAC,IAAY,EAAE,KAA0B,EAAE,KAAa;IACnE,OAAO,IAAI,CAAA;;;6JAGgJ,SAAS;oBAClJ,CAAC,KAAY,EAAE,EAAE,CAAC,gBAAgB,CAAC,KAAK,EAAE,IAAI,CAAC;oBAC/C,CAAC,KAAY,EAAE,EAAE,CAAC,gBAAgB,CAAC,KAAK,EAAE,KAAK,CAAC;aACvD,CAAC,KAAY,EAAE,EAAE,CAAC,gBAAgB,CAAC,KAAK,EAAE,KAAK,CAAC;YACjD,CAAC,KAAY,EAAE,EAAE,CAAC,gBAAgB,CAAC,KAAK,EAAE,KAAK,CAAC;mBACzC,CAAC,KAAY,EAAE,EAAE,CAAC,KAAK,CAAC,eAAe,EAAE;eAC7C,CAAC,KAAoB,EAAE,EAAE;QAClC,IAAI,KAAK,CAAC,GAAG,KAAK,OAAO;YAAE,KAAK,CAAC,eAAe,EAAE,CAAA;IACpD,CAAC;aACQ,CAAC,KAAY,EAAE,EAAE;QACxB,KAAK,CAAC,cAAc,EAAE,CAAA;QACtB,KAAK,CAAC,eAAe,EAAE,CAAA;QACvB,MAAM,MAAM,GAAG,KAAK,CAAC,aAA4B,CAAA;QACjD,iBAAiB,CAAC,IAAI,EAAE,KAAK,EAAE,GAAG,EAAE,GAAG,IAAI,MAAM,CAAC,WAAW;YAAE,MAAM,CAAC,KAAK,EAAE,CAAA,CAAC,CAAC,CAAC,CAAA;IAClF,CAAC;OACE,KAAK;IACR,CAAA;AACJ,CAAC","sourcesContent":["import { html, type TemplateResult } from 'lit'\n\nimport { entity360KindOf, entity360KindRegistered, openEntity360Kind } from './entity360-registry.js'\n\n/**\n * An entity's name as the screen draws it: a link to its 360 when its kind is registered, the plain\n * text otherwise (ADR-0067 decision 1 - an entity's name opens its 360; ADR-0071 - no dead links).\n *\n * One markup for every place a name is drawn - a grid cell, a ribbon's 「담당자」, a detail's\n * 「올린 사람」 - with the same interaction everywhere.\n *\n * The link keeps the body colour and carries a thin, faint underline set off from the text, so a\n * column of names stays calm; the line firms up under the pointer or keyboard focus. Keep this in\n * the helper as it also renders inside grid and section shadow roots. A click does not travel on:\n * inside a grid row it would start editing or select the row.\n */\nconst LINE_REST = 'color-mix(in srgb, currentColor 30%, transparent)'\nconst LINE_ACTIVE = 'color-mix(in srgb, currentColor 65%, transparent)'\n\nfunction updateDecoration(event: Event, hovered: boolean) {\n const link = event.currentTarget as HTMLElement\n link.style.textDecorationColor = hovered || link.matches(':hover, :focus-visible') ? LINE_ACTIVE : LINE_REST\n}\n\nexport function entity360Name(value: Record<string, any> | null | undefined, text?: string): TemplateResult | string {\n const label = text ?? value?.name ?? ''\n const kind = value ? entity360KindOf(value) : undefined\n\n return kind ? link(kind, value!, label) : label\n}\n\n/**\n * An identifier as the screen draws it when the screen knows its kind but holds no typed value - an\n * id inside a row (a location in a stock line), or a relation inside another 360. The same link as\n * entity360Name; plain text for a kind nobody registered, or an empty id.\n */\nexport function entity360Link(kind: string, id: string | null | undefined, text?: string): TemplateResult | string {\n const label = text ?? id ?? ''\n if (!id || !entity360KindRegistered(kind)) return label\n\n return link(kind, { id, name: label }, label)\n}\n\nfunction link(kind: string, value: Record<string, any>, label: string): TemplateResult {\n return html`<a\n href=\"#\"\n data-entity360\n style=\"color: inherit; cursor: pointer; text-decoration: underline; text-decoration-thickness: 1px; text-underline-offset: 3px; text-decoration-color: ${LINE_REST}; transition: text-decoration-color 0.12s\"\n @pointerenter=${(event: Event) => updateDecoration(event, true)}\n @pointerleave=${(event: Event) => updateDecoration(event, false)}\n @focus=${(event: Event) => updateDecoration(event, false)}\n @blur=${(event: Event) => updateDecoration(event, false)}\n @pointerdown=${(event: Event) => event.stopPropagation()}\n @keydown=${(event: KeyboardEvent) => {\n if (event.key === 'Enter') event.stopPropagation()\n }}\n @click=${(event: Event) => {\n event.preventDefault()\n event.stopPropagation()\n const opener = event.currentTarget as HTMLElement\n openEntity360Kind(kind, value, () => { if (opener.isConnected) opener.focus() })\n }}\n >${label}</a\n >`\n}\n"]}
@@ -28,6 +28,13 @@ export interface Entity360KindDeclaration {
28
28
  title(subject: Entity360Subject): string;
29
29
  /** A line under the title, if the kind has one. */
30
30
  context?(subject: Entity360Subject): string;
31
+ /**
32
+ * The open 360 is what the page talks about: while it is open the page context's `subject` is
33
+ * `{ type: kind, id }`, so the AI dock follows it (ai-assistant registerSubjectSurface). Closing
34
+ * gives the page its own subject back. Off by default - a kind the assistant has no surface for
35
+ * would only take the page's subject away.
36
+ */
37
+ assistantSubject?: boolean;
31
38
  }
32
39
  export interface Entity360Section {
33
40
  /** Unique within its kind. */
@@ -56,6 +63,8 @@ export declare function registerEntity360Section(kind: string, section: Entity36
56
63
  * the cache off) - the caller draws it as it always has.
57
64
  */
58
65
  export declare function entity360KindOf(value: unknown): string | undefined;
66
+ /** Whether a kind was declared - an identifier of an undeclared kind stays text. */
67
+ export declare function entity360KindRegistered(kind: string): boolean;
59
68
  /** The sections hung on a kind, in order. */
60
69
  export declare function entity360Sections(kind: string): Entity360Section[];
61
70
  /** What was refused, and why - for a test or a boot check to read. */
@@ -67,3 +76,10 @@ export declare function entity360Rejections(): Entity360Rejection[];
67
76
  * shows one line saying there is no 360 for it yet. The dock is never blank.
68
77
  */
69
78
  export declare function openEntity360For(value: Record<string, any>, returnFocus?: () => void): void;
79
+ /**
80
+ * Open the 360 of a kind directly - for a value known by its kind and id rather than by a GraphQL
81
+ * typename: an identifier inside a row, or a relation inside another 360.
82
+ *
83
+ * Opened from inside an open 360, the one shown is kept, and the dock's back button returns to it.
84
+ */
85
+ export declare function openEntity360Kind(kind: string, value: Record<string, any>, returnFocus?: () => void): void;
@@ -1,6 +1,7 @@
1
1
  import { html } from 'lit';
2
2
  import { i18next } from '@operato/i18n';
3
- import { showEntity360, hideEntity360, canGoBackEntity360 } from './entity360.js';
3
+ import { store, UPDATE_CONTEXT } from '@operato/shell';
4
+ import { showEntity360, hideEntity360, canGoBackEntity360, isEntity360Shown } from './entity360.js';
4
5
  const kinds = new Map();
5
6
  const kindOfTypename = new Map();
6
7
  const sections = new Map();
@@ -53,6 +54,10 @@ export function entity360KindOf(value) {
53
54
  const typename = value === null || value === void 0 ? void 0 : value.__typename;
54
55
  return typeof typename === 'string' ? kindOfTypename.get(typename) : undefined;
55
56
  }
57
+ /** Whether a kind was declared - an identifier of an undeclared kind stays text. */
58
+ export function entity360KindRegistered(kind) {
59
+ return kinds.has(kind);
60
+ }
56
61
  /** The sections hung on a kind, in order. */
57
62
  export function entity360Sections(kind) {
58
63
  var _a;
@@ -62,6 +67,11 @@ export function entity360Sections(kind) {
62
67
  export function entity360Rejections() {
63
68
  return [...rejected];
64
69
  }
70
+ /** Subjects passed through inside the dock, newest last - the dock's back button walks them. */
71
+ let trail = [];
72
+ let shown;
73
+ /** The page's own subject while a 360 stands in for it, and the page it belongs to. */
74
+ let pageSubject;
65
75
  /**
66
76
  * Open the 360 of a value.
67
77
  *
@@ -69,17 +79,36 @@ export function entity360Rejections() {
69
79
  * shows one line saying there is no 360 for it yet. The dock is never blank.
70
80
  */
71
81
  export function openEntity360For(value, returnFocus) {
72
- var _a, _b, _c, _d;
73
- const kind = (_a = entity360KindOf(value)) !== null && _a !== void 0 ? _a : '';
74
- const subject = { kind, id: String((_b = value === null || value === void 0 ? void 0 : value.id) !== null && _b !== void 0 ? _b : ''), value: value !== null && value !== void 0 ? value : {} };
82
+ var _a;
83
+ openEntity360Kind((_a = entity360KindOf(value)) !== null && _a !== void 0 ? _a : '', value, returnFocus);
84
+ }
85
+ /**
86
+ * Open the 360 of a kind directly - for a value known by its kind and id rather than by a GraphQL
87
+ * typename: an identifier inside a row, or a relation inside another 360.
88
+ *
89
+ * Opened from inside an open 360, the one shown is kept, and the dock's back button returns to it.
90
+ */
91
+ export function openEntity360Kind(kind, value, returnFocus) {
92
+ var _a;
93
+ 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 : {} };
94
+ if (!isEntity360Shown(OWNER))
95
+ trail = [];
96
+ else if (shown && (shown.kind !== subject.kind || shown.id !== subject.id))
97
+ trail.push(shown);
98
+ draw(subject, returnFocus);
99
+ }
100
+ function draw(subject, returnFocus) {
101
+ var _a, _b;
102
+ const { kind, value } = subject;
75
103
  const declaration = kinds.get(kind);
76
104
  const hung = entity360Sections(kind);
105
+ shown = subject;
77
106
  const content = hung.length
78
107
  ? html `${hung.map(section => section.render(subject))}`
79
108
  : html `<p data-not-yet>${i18next.t('text.entity360-not-yet', { defaultValue: 'there is no 360 for this kind yet.' })}</p>`;
80
109
  showEntity360(OWNER, {
81
110
  key: `${kind}:${subject.id}`,
82
- title: declaration ? declaration.title(subject) : String((_d = (_c = value === null || value === void 0 ? void 0 : value.name) !== null && _c !== void 0 ? _c : value === null || value === void 0 ? void 0 : value.id) !== null && _d !== void 0 ? _d : ''),
111
+ title: declaration ? declaration.title(subject) : String((_b = (_a = value === null || value === void 0 ? void 0 : value.name) !== null && _a !== void 0 ? _a : value === null || value === void 0 ? void 0 : value.id) !== null && _b !== void 0 ? _b : ''),
83
112
  context: (declaration === null || declaration === void 0 ? void 0 : declaration.context) ? declaration.context(subject) : '',
84
113
  content,
85
114
  labels: {
@@ -89,8 +118,42 @@ export function openEntity360For(value, returnFocus) {
89
118
  resize: i18next.t('label.resize', { defaultValue: 'resize' }),
90
119
  back: i18next.t('button.back', { defaultValue: 'back' })
91
120
  },
92
- onClose: () => hideEntity360(OWNER),
93
- onBack: canGoBackEntity360() ? () => history.back() : undefined
121
+ onClose: () => {
122
+ hideEntity360(OWNER);
123
+ trail = [];
124
+ shown = undefined;
125
+ giveSubjectBack();
126
+ },
127
+ onBack: trail.length
128
+ ? () => draw(trail.pop())
129
+ : canGoBackEntity360()
130
+ ? () => history.back()
131
+ : undefined
94
132
  }, returnFocus);
133
+ if (declaration === null || declaration === void 0 ? void 0 : declaration.assistantSubject)
134
+ takeSubject({ type: kind, id: subject.id });
135
+ else
136
+ giveSubjectBack();
137
+ }
138
+ function routeContext() {
139
+ var _a, _b, _c;
140
+ return (_c = (_b = (_a = store.getState()) === null || _a === void 0 ? void 0 : _a.route) === null || _b === void 0 ? void 0 : _b.context) !== null && _c !== void 0 ? _c : {};
141
+ }
142
+ function takeSubject(ours) {
143
+ const context = routeContext();
144
+ if (!pageSubject || pageSubject.path !== location.pathname)
145
+ pageSubject = { path: location.pathname, subject: context.subject };
146
+ pageSubject.ours = ours;
147
+ store.dispatch({ type: UPDATE_CONTEXT, context: { ...context, subject: ours } });
148
+ }
149
+ /** Only on the same page, and only while the subject is still the one the 360 put there. */
150
+ function giveSubjectBack() {
151
+ const taken = pageSubject;
152
+ pageSubject = undefined;
153
+ if (!taken || taken.path !== location.pathname)
154
+ return;
155
+ const context = routeContext();
156
+ if (context.subject === taken.ours)
157
+ store.dispatch({ type: UPDATE_CONTEXT, context: { ...context, subject: taken.subject } });
95
158
  }
96
159
  //# sourceMappingURL=entity360-registry.js.map
@@ -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 { showEntity360, hideEntity360, canGoBackEntity360 } 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\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 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\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 const kind = entity360KindOf(value) ?? ''\n const subject: Entity360Subject = { kind, id: String(value?.id ?? ''), value: value ?? {} }\n const declaration = kinds.get(kind)\n const hung = entity360Sections(kind)\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: `${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: () => hideEntity360(OWNER),\n onBack: canGoBackEntity360() ? () => history.back() : undefined\n },\n returnFocus\n )\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'\n\nimport { store, UPDATE_CONTEXT } from '@operato/shell'\n\nimport { showEntity360, hideEntity360, canGoBackEntity360, isEntity360Shown } 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\n/** Subjects passed through inside the dock, newest last - the dock's back button walks them. */\nlet trail: Entity360Subject[] = []\nlet shown: Entity360Subject | undefined\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/**\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 * Opened from inside an open 360, the one shown is kept, and the dock's back button returns to it.\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\n if (!isEntity360Shown(OWNER)) trail = []\n else if (shown && (shown.kind !== subject.kind || shown.id !== subject.id)) trail.push(shown)\n\n draw(subject, returnFocus)\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: `${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: () => {\n hideEntity360(OWNER)\n trail = []\n shown = undefined\n giveSubjectBack()\n },\n onBack: trail.length\n ? () => draw(trail.pop()!)\n : canGoBackEntity360()\n ? () => history.back()\n : undefined\n },\n returnFocus\n )\n\n if (declaration?.assistantSubject) takeSubject({ type: kind, id: subject.id })\n else giveSubjectBack()\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
+ /** Whether the dock is open for this owner - closed by its button, by another owner or by navigation, it is not. */
5
+ export declare function isEntity360Shown(owner: object): boolean;
4
6
  export declare function hideEntity360(owner: object, restoreFocus?: boolean): void;
5
7
  /** Providers validate the canonical axis and read scope before calling this function. */
6
8
  export declare function navigateEntity360(address: string): void;
@@ -25,6 +25,10 @@ export function showEntity360(owner, view, returnFocus) {
25
25
  current.returnFocus = returnFocus;
26
26
  }
27
27
  }
28
+ /** Whether the dock is open for this owner - closed by its button, by another owner or by navigation, it is not. */
29
+ export function isEntity360Shown(owner) {
30
+ return (current === null || current === void 0 ? void 0 : current.owner) === owner;
31
+ }
28
32
  export function hideEntity360(owner, restoreFocus = true) {
29
33
  if ((current === null || current === void 0 ? void 0 : current.owner) !== owner)
30
34
  return;
@@ -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\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'\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"]}
@@ -0,0 +1 @@
1
+ import '../src/index.js';
@@ -0,0 +1,78 @@
1
+ import { expect, fixture } from '@open-wc/testing';
2
+ import { html, render } from 'lit';
3
+ import { store, UPDATE_CONTEXT } from '@operato/shell';
4
+ import '../src/index.js';
5
+ import { entity360Link, openEntity360Kind, registerEntity360Kind } from '../src/index.js';
6
+ /**
7
+ * A 360 opened by kind and id, the dock's own back trail, and the assistant subject it stands in for.
8
+ *
9
+ * A screen often holds an identifier, not a typed value: a location inside a stock line, an order
10
+ * inside a task's 360. It still opens the 360 of that kind, with the same link as entity360Name.
11
+ * Following a relation inside the dock keeps the one shown, and back returns to it.
12
+ */
13
+ const settle = () => new Promise(resolve => setTimeout(resolve, 50));
14
+ let bar;
15
+ const dock = () => bar.shadowRoot.querySelector('ox-entity360-dock');
16
+ const subject = () => { var _a, _b; return (_b = (_a = store.getState().route) === null || _a === void 0 ? void 0 : _a.context) === null || _b === void 0 ? void 0 : _b.subject; };
17
+ registerEntity360Kind({ kind: 'kx-order', typenames: [], title: s => `order ${s.id}`, assistantSubject: true });
18
+ registerEntity360Kind({ kind: 'kx-place', typenames: [], title: s => `place ${s.id}`, assistantSubject: true });
19
+ registerEntity360Kind({ kind: 'kx-quiet', typenames: [], title: s => `quiet ${s.id}` });
20
+ describe('entity 360 by kind', () => {
21
+ beforeEach(async () => {
22
+ bar = await fixture(html `<ox-aside-bar></ox-aside-bar>`);
23
+ store.dispatch({ type: UPDATE_CONTEXT, context: { title: 'page', subject: { type: 'page', id: 'P' } } });
24
+ });
25
+ afterEach(() => { var _a, _b; return (_b = (_a = dock()) === null || _a === void 0 ? void 0 : _a.view) === null || _b === void 0 ? void 0 : _b.onClose(); });
26
+ it('★ an identifier of a registered kind is the same link as a name; an unregistered one stays text', async () => {
27
+ const host = document.createElement('div');
28
+ render(html `${entity360Link('kx-order', 'SO-1')}|${entity360Link('nobody', 'X-1')}|${entity360Link('kx-order', '')}`, host);
29
+ const anchors = host.querySelectorAll('a[data-entity360]');
30
+ expect(anchors.length).to.equal(1);
31
+ expect(anchors[0].textContent).to.equal('SO-1');
32
+ expect(host.textContent).to.include('X-1');
33
+ });
34
+ it('★ a click on the link opens that kind with that id', async () => {
35
+ const host = await fixture(html `<div>${entity360Link('kx-order', 'SO-2')}</div>`);
36
+ host.querySelector('a').click();
37
+ await settle();
38
+ expect(dock().view.title).to.equal('order SO-2');
39
+ });
40
+ it('★ following a relation inside the dock keeps the one shown; back returns to it', async () => {
41
+ openEntity360Kind('kx-order', { id: 'SO-3' });
42
+ await settle();
43
+ expect(dock().view.onBack).to.equal(undefined);
44
+ openEntity360Kind('kx-place', { id: 'L-1' });
45
+ await settle();
46
+ expect(dock().view.title).to.equal('place L-1');
47
+ dock().view.onBack();
48
+ await settle();
49
+ expect(dock().view.title).to.equal('order SO-3');
50
+ expect(dock().view.onBack).to.equal(undefined);
51
+ });
52
+ it('a dock opened again after closing starts with no trail', async () => {
53
+ openEntity360Kind('kx-order', { id: 'SO-4' });
54
+ await settle();
55
+ dock().view.onClose();
56
+ await settle();
57
+ openEntity360Kind('kx-place', { id: 'L-2' });
58
+ await settle();
59
+ expect(dock().view.onBack).to.equal(undefined);
60
+ });
61
+ it('★ while open, the 360 is the page subject; closing gives the page its own back', async () => {
62
+ openEntity360Kind('kx-order', { id: 'SO-5' });
63
+ await settle();
64
+ expect(subject()).to.deep.equal({ type: 'kx-order', id: 'SO-5' });
65
+ openEntity360Kind('kx-place', { id: 'L-3' });
66
+ await settle();
67
+ expect(subject()).to.deep.equal({ type: 'kx-place', id: 'L-3' });
68
+ dock().view.onClose();
69
+ await settle();
70
+ expect(subject()).to.deep.equal({ type: 'page', id: 'P' });
71
+ });
72
+ it('a kind that does not stand in for the page leaves the page subject alone', async () => {
73
+ openEntity360Kind('kx-quiet', { id: 'Q-1' });
74
+ await settle();
75
+ expect(subject()).to.deep.equal({ type: 'page', id: 'P' });
76
+ });
77
+ });
78
+ //# sourceMappingURL=entity360-kind.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"entity360-kind.test.js","sourceRoot":"","sources":["../../test/entity360-kind.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { expect, fixture } from '@open-wc/testing'\nimport { html, render } from 'lit'\nimport { store, UPDATE_CONTEXT } from '@operato/shell'\nimport '../src/index.js'\nimport { entity360Link, openEntity360Kind, registerEntity360Kind, type OxEntity360Dock } from '../src/index.js'\n\n/**\n * A 360 opened by kind and id, the dock's own back trail, and the assistant subject it stands in for.\n *\n * A screen often holds an identifier, not a typed value: a location inside a stock line, an order\n * inside a task's 360. It still opens the 360 of that kind, with the same link as entity360Name.\n * Following a relation inside the dock keeps the one shown, and back returns to it.\n */\n\nconst settle = () => new Promise(resolve => setTimeout(resolve, 50))\nlet bar: HTMLElement\nconst dock = () => bar.shadowRoot!.querySelector('ox-entity360-dock') as OxEntity360Dock\nconst subject = () => (store.getState() as any).route?.context?.subject\n\nregisterEntity360Kind({ kind: 'kx-order', typenames: [], title: s => `order ${s.id}`, assistantSubject: true })\nregisterEntity360Kind({ kind: 'kx-place', typenames: [], title: s => `place ${s.id}`, assistantSubject: true })\nregisterEntity360Kind({ kind: 'kx-quiet', typenames: [], title: s => `quiet ${s.id}` })\n\ndescribe('entity 360 by kind', () => {\n beforeEach(async () => {\n bar = await fixture(html`<ox-aside-bar></ox-aside-bar>`)\n store.dispatch({ type: UPDATE_CONTEXT, context: { title: 'page', subject: { type: 'page', id: 'P' } } })\n })\n afterEach(() => dock()?.view?.onClose())\n\n it('★ an identifier of a registered kind is the same link as a name; an unregistered one stays text', async () => {\n const host = document.createElement('div')\n render(html`${entity360Link('kx-order', 'SO-1')}|${entity360Link('nobody', 'X-1')}|${entity360Link('kx-order', '')}`, host)\n\n const anchors = host.querySelectorAll('a[data-entity360]')\n expect(anchors.length).to.equal(1)\n expect(anchors[0].textContent).to.equal('SO-1')\n expect(host.textContent).to.include('X-1')\n })\n\n it('★ a click on the link opens that kind with that id', async () => {\n const host = await fixture(html`<div>${entity360Link('kx-order', 'SO-2')}</div>`)\n ;(host.querySelector('a') as HTMLElement).click()\n await settle()\n\n expect(dock().view!.title).to.equal('order SO-2')\n })\n\n it('★ following a relation inside the dock keeps the one shown; back returns to it', async () => {\n openEntity360Kind('kx-order', { id: 'SO-3' })\n await settle()\n expect(dock().view!.onBack).to.equal(undefined)\n\n openEntity360Kind('kx-place', { id: 'L-1' })\n await settle()\n expect(dock().view!.title).to.equal('place L-1')\n\n dock().view!.onBack!()\n await settle()\n expect(dock().view!.title).to.equal('order SO-3')\n expect(dock().view!.onBack).to.equal(undefined)\n })\n\n it('a dock opened again after closing starts with no trail', async () => {\n openEntity360Kind('kx-order', { id: 'SO-4' })\n await settle()\n dock().view!.onClose()\n await settle()\n\n openEntity360Kind('kx-place', { id: 'L-2' })\n await settle()\n expect(dock().view!.onBack).to.equal(undefined)\n })\n\n it('★ while open, the 360 is the page subject; closing gives the page its own back', async () => {\n openEntity360Kind('kx-order', { id: 'SO-5' })\n await settle()\n expect(subject()).to.deep.equal({ type: 'kx-order', id: 'SO-5' })\n\n openEntity360Kind('kx-place', { id: 'L-3' })\n await settle()\n expect(subject()).to.deep.equal({ type: 'kx-place', id: 'L-3' })\n\n dock().view!.onClose()\n await settle()\n expect(subject()).to.deep.equal({ type: 'page', id: 'P' })\n })\n\n it('a kind that does not stand in for the page leaves the page subject alone', async () => {\n openEntity360Kind('kx-quiet', { id: 'Q-1' })\n await settle()\n expect(subject()).to.deep.equal({ type: 'page', id: 'P' })\n })\n})\n"]}