@operato/context 10.29.26 → 10.29.28

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.
@@ -4,7 +4,6 @@ import { LitElement } from 'lit';
4
4
  export declare class PageTitleBar extends LitElement {
5
5
  static styles: import("lit").CSSResult[];
6
6
  private _route;
7
- mobile: boolean;
8
7
  /**
9
8
  * 그린 것이 하나도 없으면 host 에 `blank` 를 붙인다 — 그러면 자리를 비운다.
10
9
  *
@@ -5,12 +5,10 @@ import { css, html, LitElement } from 'lit';
5
5
  import { customElement } from 'lit/decorators.js';
6
6
  import { openHelp } from '@operato/help';
7
7
  import { RouteController } from '@operato/shell';
8
- import { isMobileDevice } from '@operato/utils';
9
8
  let PageTitleBar = class PageTitleBar extends LitElement {
10
9
  constructor() {
11
10
  super(...arguments);
12
11
  this._route = new RouteController(this);
13
- this.mobile = isMobileDevice();
14
12
  }
15
13
  /**
16
14
  * 그린 것이 하나도 없으면 host 에 `blank` 를 붙인다 — 그러면 자리를 비운다.
@@ -29,10 +27,6 @@ let PageTitleBar = class PageTitleBar extends LitElement {
29
27
  const { icon, text = title } = typeof title === 'object' ? title : {};
30
28
  const searchable = typeof searchHandler == 'function';
31
29
  const filterable = typeof filterHandler == 'function';
32
- if (this.mobile && searchable && title) {
33
- placeholder || (placeholder = text);
34
- title = '';
35
- }
36
30
  /*
37
31
  * ── 화면이 자기 제목을 그리는 경우 ────────────────────────────────────────
38
32
  *
@@ -156,7 +150,7 @@ PageTitleBar.styles = [
156
150
  display: flex;
157
151
  padding: var(--spacing-small);
158
152
  font: var(--context-toolbar-title);
159
- color: var(--title-text-color);
153
+ color: var(--context-toolbar-title-color, var(--md-sys-color-on-surface));
160
154
  }
161
155
 
162
156
  [titler] > * {
@@ -252,6 +246,14 @@ PageTitleBar.styles = [
252
246
  [search] > md-icon:not([help]):hover {
253
247
  color: var(--ui-muted, #57676f);
254
248
  }
249
+
250
+ /*
251
+ * The screen's name always shows (2026-10-12). A narrow window used to drop the title for the
252
+ * search pill; now the pill moves to the next line when the row is too short, still at the right.
253
+ */
254
+ :host {
255
+ flex-wrap: wrap;
256
+ }
255
257
  `
256
258
  ];
257
259
  PageTitleBar = __decorate([
@@ -1 +1 @@
1
- {"version":3,"file":"ox-page-title-bar.js","sourceRoot":"","sources":["../../../src/tools/ox-page-title-bar.ts"],"names":[],"mappings":"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","sourcesContent":["import '@material/web/icon/icon.js'\nimport '@operato/input/ox-input-search.js'\n\nimport { css, html, LitElement } from 'lit'\nimport { customElement } from 'lit/decorators.js'\nimport { openHelp } from '@operato/help'\nimport { RouteController } from '@operato/shell'\nimport { isMobileDevice } from '@operato/utils'\n\n@customElement('ox-page-title-bar')\nexport class PageTitleBar extends LitElement {\n static styles = [\n css`\n :host {\n display: flex;\n align-items: center;\n /*\n * 줄 높이를 여기서 잡는다. 전에는 제목의 위 여백을 크게 주어(padding-top: medium) 줄을\n * 벌렸는데, 그러면 **제목 글자가 자기 상자 안에서 아래로 내려앉는다.** 옆의 검색은 가운데에\n * 있으니 둘이 어긋나 보였다. [확인함] 제목 상자 41x36 여백 8px 4px 4px. 2026-09-02\n *\n * 줄의 높이는 줄이 갖고, 제목은 위아래 같은 여백을 갖는다.\n */\n min-height: 36px;\n /*\n * ── 그릴 것이 없으면 자리도 없다 ──────────────────────────────────────\n * 제목도 검색도 필터도 없는 화면에서 이 요소가 **자식 0개인 채로 36px 을 차지**했다.\n * 배우기 문서에서 눈에 걸렸다 — 제목 줄을 끄자 문서 위에 빈 띠가 남았다.\n * [확인함] 2026-09-04 · 높이 36 · 자식 0\n *\n * min-height 는 그릴 것이 있을 때의 줄 높이다. 없을 때는 아래 [blank] 가 걷는다.\n */\n /*\n * 줄의 숨 쉴 자리 — **위와 오른쪽을 같게.**\n *\n * 전에는 제목의 위 여백(padding-top: medium)이 그 일을 하고 있었다. 그것을 걷어 글자를 가운데로\n * 맞추자 줄이 탭 바에 붙고, 알약은 원래부터 오른쪽 끝에 붙어 있었다 — 위 0 · 오른쪽 0.\n * [확인함] 2026-09-02\n *\n * 왼쪽은 화면의 여백(--ui-gutter)에 맞춘다 — 제목 글자가 본문 첫 글자와 같은 선에 선다.\n * 제목 상자가 자기 여백(spacing-small)을 가지므로 그만큼 뺀다. 전에는 0 이어서 문서 화면에서\n * 제목(x214)과 본문(x229)이 15px 어긋났다. [확인함 :4000 1440 창] 2026-10-05\n */\n padding: var(--spacing-medium) var(--spacing-medium) var(--spacing-small)\n calc(var(--ui-gutter, 16px) - var(--spacing-small, 4px));\n box-sizing: border-box;\n text-transform: capitalize;\n\n --help-icon-color: var(--md-sys-color-on-surface);\n --help-icon-hover-color: var(--md-sys-color-on-surface-variant);\n /*\n * ── 0.2 는 안 보인다 (2026-09-05) ────────────────────────────────\n * 도움말은 「있는 줄 알아야」 누른다. 20% 로 두면 야간 모드에서 대비 1.78:1 이고\n * (실측: 성숙도 화면 제목 옆), 밝은 모드도 비슷하다. 선택해 볼 생각을 못 한다.\n *\n * 0.5 면 밝은 화면 3.4 · 어두운 화면 3.9 로 UI 요소 기준(3:1)을 넘고, 제목보다는\n * 여전히 뒤에 선다.\n */\n --help-icon-opacity: 0.5;\n /* 마우스가 오면 또렷해진다. 전에는 이 이름이 선언되어 있지 않아 무효 치환 덕에\n 우연히 1 이 되고 있었다. */\n --help-icon-hover-opacity: 1;\n --help-icon-size: 20px;\n\n --md-icon-size: 20px;\n\n --input-search-padding: var(--spacing-small);\n --input-search-font: normal 14px var(--theme-font);\n }\n\n /*\n * 아무것도 그리지 않았다 — 자리를 비운다. updated() 가 그려진 것을 세어 붙인다.\n *\n * 왜 CSS 로만 못 하나: :host(:empty) 는 **바깥 DOM 의 자식**을 보므로 이 부품에서는 늘\n * 참이다(쓰는 쪽이 자식을 넣지 않는다). 그려진 것은 그림자 안에 있어서 세어야 한다.\n */\n :host([blank]) {\n display: none;\n }\n\n [titler] {\n display: flex;\n padding: var(--spacing-small);\n font: var(--context-toolbar-title);\n color: var(--title-text-color);\n }\n\n [titler] > * {\n align-self: center;\n }\n\n /*\n * One line under the title that says what the screen is for (context.description). It takes\n * the whole row, so the search pill stays on the title's line. Without the key nothing is\n * drawn and the bar is exactly as before (ADR-0060 decision 8).\n */\n :host([described]) {\n flex-wrap: wrap;\n }\n\n [description] {\n order: 10;\n flex-basis: 100%;\n padding: 0 var(--spacing-small) var(--spacing-small);\n font-size: var(--ui-t-body, 13px);\n line-height: 1.45;\n color: var(--ui-muted, var(--md-sys-color-on-surface-variant));\n text-transform: none;\n }\n\n [help] {\n opacity: var(--help-icon-opacity);\n font-size: var(--help-icon-size);\n color: var(--help-icon-color);\n }\n\n [help]:hover {\n cursor: help;\n opacity: var(--help-icon-hover-opacity);\n color: var(--help-icon-hover-color);\n }\n\n [search] [help] {\n color: var(--md-sys-color-on-secondary-container);\n }\n\n [search] {\n display: flex;\n justify-content: space-between;\n align-items: center;\n\n /*\n * 알약의 높이를 정한다. 안의 입력이 정하게 두면 23px 이 되어 제목 줄보다 낮고, 알약이라\n * 그 차이가 눈에 걸린다.\n */\n min-height: 28px;\n box-sizing: border-box;\n\n align-self: center;\n /*\n * 검색은 강조 동작이 아니라 현재 목록을 좁히는 도구다. primary 의 반대색을 면으로\n * 쓰면 다크 도메인에서 밝은 알약으로 뒤집혀 제목 줄보다 먼저 읽힌다. 표준 surface\n * 단계 안에서만 분리해 다른 애플리케이션의 검색과 같은 톤을 유지한다.\n */\n color: var(--md-sys-color-on-surface);\n background-color: var(--md-sys-color-surface-container-low);\n border-radius: var(--md-sys-shape-corner-full);\n margin-left: auto;\n padding: 0 var(--spacing-medium);\n }\n\n [search] > * {\n align-self: center;\n }\n\n /*\n * 알약 안의 장치는 **한 단 흐리다** — 돋보기와 필터 여닫기.\n *\n * 둘 다 값이 아니라 값을 다루는 장치다. 입력한 글자와 같은 색이면 빈 알약에서 장치만 눈에 걸린다.\n * 마우스가 오면 진해져서 누를 수 있음을 말한다.\n */\n [search] ox-input-search {\n /*\n * 검색 알약이 이미 표면과 모양을 책임진다. 입력 자체의 기본 surface-variant 배경을\n * 그대로 두면 알약 안에 또 하나의 상자가 생겨 경계가 도드라진다.\n */\n --ox-input-search-background-color: transparent;\n --ox-input-search-color: var(--md-sys-color-on-surface);\n --ox-input-search-icon-color: var(--ui-faint, #8a959c);\n }\n\n [search] > md-icon:not([help]) {\n color: var(--ui-faint, #8a959c);\n cursor: pointer;\n transition: color 0.12s ease;\n }\n\n [search] > md-icon:not([help]):hover {\n color: var(--ui-muted, #57676f);\n }\n `\n ]\n\n private _route = new RouteController(this)\n\n mobile = isMobileDevice()\n\n /**\n * 그린 것이 하나도 없으면 host 에 `blank` 를 붙인다 — 그러면 자리를 비운다.\n *\n * 그리는 것은 제목 · 도움 · 검색·필터 알약 셋뿐이고 모두 요소 하나를 낸다. 무엇 때문에\n * 비었는지 따지지 않고 **그려진 것을 센다.** 조건이 늘어도 이 판단은 그대로 맞다.\n */\n updated() {\n this.toggleAttribute('blank', this.renderRoot.querySelector('div, md-icon') === null)\n this.toggleAttribute('described', this.renderRoot.querySelector('[description]') !== null)\n }\n\n render() {\n var { search, filter, title, help, heading, description } = this._route.context || ({} as any)\n var { value = '', handler: searchHandler, placeholder = '', autofocus = true } = search || ({} as any)\n const { handler: filterHandler } = filter || ({} as any)\n const { icon, text = title } = typeof title === 'object' ? title : ({} as any)\n const searchable = typeof searchHandler == 'function'\n const filterable = typeof filterHandler == 'function'\n\n if (this.mobile && searchable && title) {\n placeholder ||= text\n title = ''\n }\n\n /*\n * ── 화면이 자기 제목을 그리는 경우 ────────────────────────────────────────\n *\n * `context.title` 은 **화면의 이름**이고, 그 이름을 두 곳이 읽는다.\n *\n * MDI 탭 여러 화면 중 어느 것인가\n * 이 제목 줄 지금 읽는 것이 무엇인가\n *\n * 대개 둘 다 필요하다. 그런데 **화면이 이미 자기 제목을 크게 그리는 경우**가 있다 — 배우기\n * 문서가 그렇다. 문서 맨 위에 「공장의 모든 사건을 …」 같은 제목이 있고, 그 위에 이 줄이\n * 「배우기」를 또 얹고, 탭에도 「배우기」가 있다. **같은 말이 세 번이다.**\n *\n * 그때 title 을 빼면 탭 이름도 함께 빈다(page-mdibar 가 같은 값을 읽는다). 그래서 이름은\n * 그대로 두고 **이 줄만 끄는 방법**을 둔다. 이름은 하나이고, 그리는 자리마다 켜고 끈다.\n *\n * get context() { return { title: '배우기', heading: false } }\n *\n * 제목이 없으면 도움 아이콘도 걷힌다 — 아래 조건이 이미 `help && title` 이다. 문서 자체가\n * 도움인 화면에서 그 아이콘은 같은 말의 네 번째다.\n */\n if (heading === false) {\n title = ''\n }\n\n return html`\n ${title && description ? html`<div description>${description}</div>` : html``}\n ${title ? html` <div titler>${icon ? html`<md-icon>${icon}</md-icon>&nbsp;` : html``}${text}</div> ` : html``}\n ${help && title ? html` &nbsp;<md-icon @click=${(e: MouseEvent) => openHelp(help)} help>help</md-icon> ` : html``}\n ${searchable || filterable\n ? html`\n <div search>\n ${searchable\n ? html` <ox-input-search\n .placeholder=${placeholder}\n .value=${value || ''}\n ?autofocus=${autofocus}\n @change=${(e: Event) => {\n searchHandler((e.target as any).value)\n }}\n ></ox-input-search>`\n : html``}\n ${!title && help && searchable\n ? html`<md-icon @click=${(e: MouseEvent) => openHelp(help)} help>help</md-icon>`\n : html``}\n ${filterable ? html`<md-icon @click=${(e: MouseEvent) => filterHandler()}>unfold_more</md-icon>` : html``}\n </div>\n `\n : html``}\n `\n }\n}\n"]}
1
+ {"version":3,"file":"ox-page-title-bar.js","sourceRoot":"","sources":["../../../src/tools/ox-page-title-bar.ts"],"names":[],"mappings":"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","sourcesContent":["import '@material/web/icon/icon.js'\nimport '@operato/input/ox-input-search.js'\n\nimport { css, html, LitElement } from 'lit'\nimport { customElement } from 'lit/decorators.js'\nimport { openHelp } from '@operato/help'\nimport { RouteController } from '@operato/shell'\n\n@customElement('ox-page-title-bar')\nexport class PageTitleBar extends LitElement {\n static styles = [\n css`\n :host {\n display: flex;\n align-items: center;\n /*\n * 줄 높이를 여기서 잡는다. 전에는 제목의 위 여백을 크게 주어(padding-top: medium) 줄을\n * 벌렸는데, 그러면 **제목 글자가 자기 상자 안에서 아래로 내려앉는다.** 옆의 검색은 가운데에\n * 있으니 둘이 어긋나 보였다. [확인함] 제목 상자 41x36 여백 8px 4px 4px. 2026-09-02\n *\n * 줄의 높이는 줄이 갖고, 제목은 위아래 같은 여백을 갖는다.\n */\n min-height: 36px;\n /*\n * ── 그릴 것이 없으면 자리도 없다 ──────────────────────────────────────\n * 제목도 검색도 필터도 없는 화면에서 이 요소가 **자식 0개인 채로 36px 을 차지**했다.\n * 배우기 문서에서 눈에 걸렸다 — 제목 줄을 끄자 문서 위에 빈 띠가 남았다.\n * [확인함] 2026-09-04 · 높이 36 · 자식 0\n *\n * min-height 는 그릴 것이 있을 때의 줄 높이다. 없을 때는 아래 [blank] 가 걷는다.\n */\n /*\n * 줄의 숨 쉴 자리 — **위와 오른쪽을 같게.**\n *\n * 전에는 제목의 위 여백(padding-top: medium)이 그 일을 하고 있었다. 그것을 걷어 글자를 가운데로\n * 맞추자 줄이 탭 바에 붙고, 알약은 원래부터 오른쪽 끝에 붙어 있었다 — 위 0 · 오른쪽 0.\n * [확인함] 2026-09-02\n *\n * 왼쪽은 화면의 여백(--ui-gutter)에 맞춘다 — 제목 글자가 본문 첫 글자와 같은 선에 선다.\n * 제목 상자가 자기 여백(spacing-small)을 가지므로 그만큼 뺀다. 전에는 0 이어서 문서 화면에서\n * 제목(x214)과 본문(x229)이 15px 어긋났다. [확인함 :4000 1440 창] 2026-10-05\n */\n padding: var(--spacing-medium) var(--spacing-medium) var(--spacing-small)\n calc(var(--ui-gutter, 16px) - var(--spacing-small, 4px));\n box-sizing: border-box;\n text-transform: capitalize;\n\n --help-icon-color: var(--md-sys-color-on-surface);\n --help-icon-hover-color: var(--md-sys-color-on-surface-variant);\n /*\n * ── 0.2 는 안 보인다 (2026-09-05) ────────────────────────────────\n * 도움말은 「있는 줄 알아야」 누른다. 20% 로 두면 야간 모드에서 대비 1.78:1 이고\n * (실측: 성숙도 화면 제목 옆), 밝은 모드도 비슷하다. 선택해 볼 생각을 못 한다.\n *\n * 0.5 면 밝은 화면 3.4 · 어두운 화면 3.9 로 UI 요소 기준(3:1)을 넘고, 제목보다는\n * 여전히 뒤에 선다.\n */\n --help-icon-opacity: 0.5;\n /* 마우스가 오면 또렷해진다. 전에는 이 이름이 선언되어 있지 않아 무효 치환 덕에\n 우연히 1 이 되고 있었다. */\n --help-icon-hover-opacity: 1;\n --help-icon-size: 20px;\n\n --md-icon-size: 20px;\n\n --input-search-padding: var(--spacing-small);\n --input-search-font: normal 14px var(--theme-font);\n }\n\n /*\n * 아무것도 그리지 않았다 — 자리를 비운다. updated() 가 그려진 것을 세어 붙인다.\n *\n * 왜 CSS 로만 못 하나: :host(:empty) 는 **바깥 DOM 의 자식**을 보므로 이 부품에서는 늘\n * 참이다(쓰는 쪽이 자식을 넣지 않는다). 그려진 것은 그림자 안에 있어서 세어야 한다.\n */\n :host([blank]) {\n display: none;\n }\n\n [titler] {\n display: flex;\n padding: var(--spacing-small);\n font: var(--context-toolbar-title);\n color: var(--context-toolbar-title-color, var(--md-sys-color-on-surface));\n }\n\n [titler] > * {\n align-self: center;\n }\n\n /*\n * One line under the title that says what the screen is for (context.description). It takes\n * the whole row, so the search pill stays on the title's line. Without the key nothing is\n * drawn and the bar is exactly as before (ADR-0060 decision 8).\n */\n :host([described]) {\n flex-wrap: wrap;\n }\n\n [description] {\n order: 10;\n flex-basis: 100%;\n padding: 0 var(--spacing-small) var(--spacing-small);\n font-size: var(--ui-t-body, 13px);\n line-height: 1.45;\n color: var(--ui-muted, var(--md-sys-color-on-surface-variant));\n text-transform: none;\n }\n\n [help] {\n opacity: var(--help-icon-opacity);\n font-size: var(--help-icon-size);\n color: var(--help-icon-color);\n }\n\n [help]:hover {\n cursor: help;\n opacity: var(--help-icon-hover-opacity);\n color: var(--help-icon-hover-color);\n }\n\n [search] [help] {\n color: var(--md-sys-color-on-secondary-container);\n }\n\n [search] {\n display: flex;\n justify-content: space-between;\n align-items: center;\n\n /*\n * 알약의 높이를 정한다. 안의 입력이 정하게 두면 23px 이 되어 제목 줄보다 낮고, 알약이라\n * 그 차이가 눈에 걸린다.\n */\n min-height: 28px;\n box-sizing: border-box;\n\n align-self: center;\n /*\n * 검색은 강조 동작이 아니라 현재 목록을 좁히는 도구다. primary 의 반대색을 면으로\n * 쓰면 다크 도메인에서 밝은 알약으로 뒤집혀 제목 줄보다 먼저 읽힌다. 표준 surface\n * 단계 안에서만 분리해 다른 애플리케이션의 검색과 같은 톤을 유지한다.\n */\n color: var(--md-sys-color-on-surface);\n background-color: var(--md-sys-color-surface-container-low);\n border-radius: var(--md-sys-shape-corner-full);\n margin-left: auto;\n padding: 0 var(--spacing-medium);\n }\n\n [search] > * {\n align-self: center;\n }\n\n /*\n * 알약 안의 장치는 **한 단 흐리다** — 돋보기와 필터 여닫기.\n *\n * 둘 다 값이 아니라 값을 다루는 장치다. 입력한 글자와 같은 색이면 빈 알약에서 장치만 눈에 걸린다.\n * 마우스가 오면 진해져서 누를 수 있음을 말한다.\n */\n [search] ox-input-search {\n /*\n * 검색 알약이 이미 표면과 모양을 책임진다. 입력 자체의 기본 surface-variant 배경을\n * 그대로 두면 알약 안에 또 하나의 상자가 생겨 경계가 도드라진다.\n */\n --ox-input-search-background-color: transparent;\n --ox-input-search-color: var(--md-sys-color-on-surface);\n --ox-input-search-icon-color: var(--ui-faint, #8a959c);\n }\n\n [search] > md-icon:not([help]) {\n color: var(--ui-faint, #8a959c);\n cursor: pointer;\n transition: color 0.12s ease;\n }\n\n [search] > md-icon:not([help]):hover {\n color: var(--ui-muted, #57676f);\n }\n\n /*\n * The screen's name always shows (2026-10-12). A narrow window used to drop the title for the\n * search pill; now the pill moves to the next line when the row is too short, still at the right.\n */\n :host {\n flex-wrap: wrap;\n }\n `\n ]\n\n private _route = new RouteController(this)\n\n /**\n * 그린 것이 하나도 없으면 host 에 `blank` 를 붙인다 — 그러면 자리를 비운다.\n *\n * 그리는 것은 제목 · 도움 · 검색·필터 알약 셋뿐이고 모두 요소 하나를 낸다. 무엇 때문에\n * 비었는지 따지지 않고 **그려진 것을 센다.** 조건이 늘어도 이 판단은 그대로 맞다.\n */\n updated() {\n this.toggleAttribute('blank', this.renderRoot.querySelector('div, md-icon') === null)\n this.toggleAttribute('described', this.renderRoot.querySelector('[description]') !== null)\n }\n\n render() {\n var { search, filter, title, help, heading, description } = this._route.context || ({} as any)\n var { value = '', handler: searchHandler, placeholder = '', autofocus = true } = search || ({} as any)\n const { handler: filterHandler } = filter || ({} as any)\n const { icon, text = title } = typeof title === 'object' ? title : ({} as any)\n const searchable = typeof searchHandler == 'function'\n const filterable = typeof filterHandler == 'function'\n\n /*\n * ── 화면이 자기 제목을 그리는 경우 ────────────────────────────────────────\n *\n * `context.title` 은 **화면의 이름**이고, 그 이름을 두 곳이 읽는다.\n *\n * MDI 탭 여러 화면 중 어느 것인가\n * 이 제목 줄 지금 읽는 것이 무엇인가\n *\n * 대개 둘 다 필요하다. 그런데 **화면이 이미 자기 제목을 크게 그리는 경우**가 있다 — 배우기\n * 문서가 그렇다. 문서 맨 위에 「공장의 모든 사건을 …」 같은 제목이 있고, 그 위에 이 줄이\n * 「배우기」를 또 얹고, 탭에도 「배우기」가 있다. **같은 말이 세 번이다.**\n *\n * 그때 title 을 빼면 탭 이름도 함께 빈다(page-mdibar 가 같은 값을 읽는다). 그래서 이름은\n * 그대로 두고 **이 줄만 끄는 방법**을 둔다. 이름은 하나이고, 그리는 자리마다 켜고 끈다.\n *\n * get context() { return { title: '배우기', heading: false } }\n *\n * 제목이 없으면 도움 아이콘도 걷힌다 — 아래 조건이 이미 `help && title` 이다. 문서 자체가\n * 도움인 화면에서 그 아이콘은 같은 말의 네 번째다.\n */\n if (heading === false) {\n title = ''\n }\n\n return html`\n ${title && description ? html`<div description>${description}</div>` : html``}\n ${title ? html` <div titler>${icon ? html`<md-icon>${icon}</md-icon>&nbsp;` : html``}${text}</div> ` : html``}\n ${help && title ? html` &nbsp;<md-icon @click=${(e: MouseEvent) => openHelp(help)} help>help</md-icon> ` : html``}\n ${searchable || filterable\n ? html`\n <div search>\n ${searchable\n ? html` <ox-input-search\n .placeholder=${placeholder}\n .value=${value || ''}\n ?autofocus=${autofocus}\n @change=${(e: Event) => {\n searchHandler((e.target as any).value)\n }}\n ></ox-input-search>`\n : html``}\n ${!title && help && searchable\n ? html`<md-icon @click=${(e: MouseEvent) => openHelp(help)} help>help</md-icon>`\n : html``}\n ${filterable ? html`<md-icon @click=${(e: MouseEvent) => filterHandler()}>unfold_more</md-icon>` : html``}\n </div>\n `\n : html``}\n `\n }\n}\n"]}
@@ -48,4 +48,43 @@ describe('ox-page-title-bar gutter', () => {
48
48
  expect(Math.round(offset)).to.equal(16);
49
49
  });
50
50
  });
51
+ describe('ox-page-title-bar narrow window', () => {
52
+ /*
53
+ * The screen's name always shows. A narrow window used to drop the title for the search pill
54
+ * (2026-10-12, operato-board); now the pill moves under the title.
55
+ */
56
+ it('★ keeps the title and puts the search pill on the next line', async () => {
57
+ var _a;
58
+ const host = await fixture(html `<div style="width: 240px"><ox-page-title-bar></ox-page-title-bar></div>`);
59
+ store.dispatch({
60
+ type: 'UPDATE_CONTEXT',
61
+ context: { title: 'scenario list', search: { handler: () => { }, placeholder: 'search scenarios by name' } }
62
+ });
63
+ const el = host.querySelector('ox-page-title-bar');
64
+ await el.updateComplete;
65
+ await el.updateComplete;
66
+ const titler = el.shadowRoot.querySelector('[titler]');
67
+ const search = el.shadowRoot.querySelector('[search]');
68
+ expect((_a = titler === null || titler === void 0 ? void 0 : titler.textContent) === null || _a === void 0 ? void 0 : _a.trim()).to.equal('scenario list');
69
+ expect(search.getBoundingClientRect().top).to.be.at.least(titler.getBoundingClientRect().bottom - 1);
70
+ expect(search.getBoundingClientRect().right).to.be.at.most(el.getBoundingClientRect().right + 1);
71
+ });
72
+ });
73
+ describe('ox-page-title-bar title style', () => {
74
+ /*
75
+ * The screen title reads like the New Board page's heading (2026-10-12): the theme gives the font
76
+ * (--context-toolbar-title) and the colour (--context-toolbar-title-color).
77
+ */
78
+ it('★ takes its font and colour from the title variables', async () => {
79
+ const host = await fixture(html `<div style="--context-toolbar-title: 600 20px sans-serif; --context-toolbar-title-color: rgb(27, 27, 27); --title-text-color: rgb(100, 85, 80)"><ox-page-title-bar></ox-page-title-bar></div>`);
80
+ store.dispatch({ type: 'UPDATE_CONTEXT', context: { title: 'board list' } });
81
+ const el = host.querySelector('ox-page-title-bar');
82
+ await el.updateComplete;
83
+ await el.updateComplete;
84
+ const style = getComputedStyle(el.shadowRoot.querySelector('[titler]'));
85
+ expect(style.fontWeight).to.equal('600');
86
+ expect(style.fontSize).to.equal('20px');
87
+ expect(style.color).to.equal('rgb(27, 27, 27)');
88
+ });
89
+ });
51
90
  //# sourceMappingURL=page-title-description.test.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"page-title-description.test.js","sourceRoot":"","sources":["../../test/page-title-description.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { expect, fixture } from '@open-wc/testing'\nimport { html } from 'lit'\n\nimport { store } from '@operato/shell'\n\nimport '../src/tools/ox-page-title-bar.js'\n\n/**\n * `context.description` draws one line under the screen title (ADR-0060 decision 8). A screen that\n * does not give it must look exactly as before: no line, no wrapping.\n */\n\nasync function bar(context: Record<string, unknown>) {\n store.dispatch({ type: 'UPDATE_CONTEXT', context } as any)\n const el = await fixture<HTMLElement & { updateComplete: Promise<unknown> }>(html`<ox-page-title-bar></ox-page-title-bar>`)\n await el.updateComplete\n await el.updateComplete\n return el\n}\n\ndescribe('ox-page-title-bar description', () => {\n it('draws the description under the title', async () => {\n const el = await bar({ title: 'work orders', description: 'orders released to the floor today' })\n const line = el.shadowRoot!.querySelector('[description]')\n\n expect(line?.textContent).to.equal('orders released to the floor today')\n expect(el.hasAttribute('described')).to.equal(true)\n expect(line!.getBoundingClientRect().top).to.be.greaterThan(el.shadowRoot!.querySelector('[titler]')!.getBoundingClientRect().top)\n })\n\n it('★ draws nothing more when the screen gives no description', async () => {\n const el = await bar({ title: 'work orders' })\n\n expect(el.shadowRoot!.querySelector('[description]') === null).to.equal(true)\n expect(el.hasAttribute('described')).to.equal(false)\n })\n\n it('leaves the description out when the screen turns the title row off', async () => {\n const el = await bar({ title: 'learn', heading: false, description: 'what the platform does' })\n\n expect(el.shadowRoot!.querySelector('[description]') === null).to.equal(true)\n })\n})\n\ndescribe('ox-page-title-bar gutter', () => {\n /*\n * The title's first letter stands on the same line as the page's first letter: the bar's left\n * padding plus the title box's own 4px equals the page gutter. It used to be 0 + 4, and on a\n * document screen the title (x214) and the content (x229) were 15px apart (2026-10-05, :4000).\n */\n it('puts the title text at the page gutter', async () => {\n const host = await fixture<HTMLElement>(html`<div style=\"--ui-gutter: 16px; --spacing-small: 4px; --spacing-medium: 8px\"><ox-page-title-bar></ox-page-title-bar></div>`)\n store.dispatch({ type: 'UPDATE_CONTEXT', context: { title: 'maintenance' } } as any)\n const el = host.querySelector('ox-page-title-bar') as HTMLElement & { updateComplete: Promise<unknown> }\n await el.updateComplete\n await el.updateComplete\n const titler = el.shadowRoot!.querySelector('[titler]') as HTMLElement\n const offset = titler.getBoundingClientRect().left + parseFloat(getComputedStyle(titler).paddingLeft) - el.getBoundingClientRect().left\n\n expect(Math.round(offset)).to.equal(16)\n })\n})\n"]}
1
+ {"version":3,"file":"page-title-description.test.js","sourceRoot":"","sources":["../../test/page-title-description.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { expect, fixture } from '@open-wc/testing'\nimport { html } from 'lit'\n\nimport { store } from '@operato/shell'\n\nimport '../src/tools/ox-page-title-bar.js'\n\n/**\n * `context.description` draws one line under the screen title (ADR-0060 decision 8). A screen that\n * does not give it must look exactly as before: no line, no wrapping.\n */\n\nasync function bar(context: Record<string, unknown>) {\n store.dispatch({ type: 'UPDATE_CONTEXT', context } as any)\n const el = await fixture<HTMLElement & { updateComplete: Promise<unknown> }>(html`<ox-page-title-bar></ox-page-title-bar>`)\n await el.updateComplete\n await el.updateComplete\n return el\n}\n\ndescribe('ox-page-title-bar description', () => {\n it('draws the description under the title', async () => {\n const el = await bar({ title: 'work orders', description: 'orders released to the floor today' })\n const line = el.shadowRoot!.querySelector('[description]')\n\n expect(line?.textContent).to.equal('orders released to the floor today')\n expect(el.hasAttribute('described')).to.equal(true)\n expect(line!.getBoundingClientRect().top).to.be.greaterThan(el.shadowRoot!.querySelector('[titler]')!.getBoundingClientRect().top)\n })\n\n it('★ draws nothing more when the screen gives no description', async () => {\n const el = await bar({ title: 'work orders' })\n\n expect(el.shadowRoot!.querySelector('[description]') === null).to.equal(true)\n expect(el.hasAttribute('described')).to.equal(false)\n })\n\n it('leaves the description out when the screen turns the title row off', async () => {\n const el = await bar({ title: 'learn', heading: false, description: 'what the platform does' })\n\n expect(el.shadowRoot!.querySelector('[description]') === null).to.equal(true)\n })\n})\n\ndescribe('ox-page-title-bar gutter', () => {\n /*\n * The title's first letter stands on the same line as the page's first letter: the bar's left\n * padding plus the title box's own 4px equals the page gutter. It used to be 0 + 4, and on a\n * document screen the title (x214) and the content (x229) were 15px apart (2026-10-05, :4000).\n */\n it('puts the title text at the page gutter', async () => {\n const host = await fixture<HTMLElement>(html`<div style=\"--ui-gutter: 16px; --spacing-small: 4px; --spacing-medium: 8px\"><ox-page-title-bar></ox-page-title-bar></div>`)\n store.dispatch({ type: 'UPDATE_CONTEXT', context: { title: 'maintenance' } } as any)\n const el = host.querySelector('ox-page-title-bar') as HTMLElement & { updateComplete: Promise<unknown> }\n await el.updateComplete\n await el.updateComplete\n const titler = el.shadowRoot!.querySelector('[titler]') as HTMLElement\n const offset = titler.getBoundingClientRect().left + parseFloat(getComputedStyle(titler).paddingLeft) - el.getBoundingClientRect().left\n\n expect(Math.round(offset)).to.equal(16)\n })\n})\n\ndescribe('ox-page-title-bar narrow window', () => {\n /*\n * The screen's name always shows. A narrow window used to drop the title for the search pill\n * (2026-10-12, operato-board); now the pill moves under the title.\n */\n it('★ keeps the title and puts the search pill on the next line', async () => {\n const host = await fixture<HTMLElement>(html`<div style=\"width: 240px\"><ox-page-title-bar></ox-page-title-bar></div>`)\n store.dispatch({\n type: 'UPDATE_CONTEXT',\n context: { title: 'scenario list', search: { handler: () => {}, placeholder: 'search scenarios by name' } }\n } as any)\n const el = host.querySelector('ox-page-title-bar') as HTMLElement & { updateComplete: Promise<unknown> }\n await el.updateComplete\n await el.updateComplete\n const titler = el.shadowRoot!.querySelector('[titler]') as HTMLElement\n const search = el.shadowRoot!.querySelector('[search]') as HTMLElement\n\n expect(titler?.textContent?.trim()).to.equal('scenario list')\n expect(search.getBoundingClientRect().top).to.be.at.least(titler.getBoundingClientRect().bottom - 1)\n expect(search.getBoundingClientRect().right).to.be.at.most(el.getBoundingClientRect().right + 1)\n })\n})\n\ndescribe('ox-page-title-bar title style', () => {\n /*\n * The screen title reads like the New Board page's heading (2026-10-12): the theme gives the font\n * (--context-toolbar-title) and the colour (--context-toolbar-title-color).\n */\n it('★ takes its font and colour from the title variables', async () => {\n const host = await fixture<HTMLElement>(\n html`<div style=\"--context-toolbar-title: 600 20px sans-serif; --context-toolbar-title-color: rgb(27, 27, 27); --title-text-color: rgb(100, 85, 80)\"><ox-page-title-bar></ox-page-title-bar></div>`\n )\n store.dispatch({ type: 'UPDATE_CONTEXT', context: { title: 'board list' } } as any)\n const el = host.querySelector('ox-page-title-bar') as HTMLElement & { updateComplete: Promise<unknown> }\n await el.updateComplete\n await el.updateComplete\n const style = getComputedStyle(el.shadowRoot!.querySelector('[titler]') as HTMLElement)\n\n expect(style.fontWeight).to.equal('600')\n expect(style.fontSize).to.equal('20px')\n expect(style.color).to.equal('rgb(27, 27, 27)')\n })\n})\n"]}