@operato/help 10.29.29 → 10.29.31
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +17 -0
- package/coverage/lcov-report/base.css +224 -0
- package/coverage/lcov-report/block-navigation.js +87 -0
- package/coverage/lcov-report/favicon.png +0 -0
- package/coverage/lcov-report/index.html +146 -0
- package/coverage/lcov-report/prettify.css +1 -0
- package/coverage/lcov-report/prettify.js +2 -0
- package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
- package/coverage/lcov-report/sorter.js +210 -0
- package/coverage/lcov.info +1277 -0
- package/dist/src/components/ox-help-spot.d.ts +6 -2
- package/dist/src/components/ox-help-spot.js +62 -1
- package/dist/src/components/ox-help-spot.js.map +1 -1
- package/dist/test/ox-doc.test.js +18 -0
- package/dist/test/ox-doc.test.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +7 -7
- package/test/ox-doc.test.ts +22 -0
|
@@ -1,16 +1,20 @@
|
|
|
1
|
-
import { LitElement, type TemplateResult } from 'lit';
|
|
1
|
+
import { LitElement, nothing, type PropertyValues, type TemplateResult } from 'lit';
|
|
2
2
|
import './ox-doc.js';
|
|
3
3
|
export declare class OxHelpSpot extends LitElement {
|
|
4
4
|
topic: string;
|
|
5
5
|
section: string;
|
|
6
6
|
label: string;
|
|
7
7
|
private open;
|
|
8
|
+
private written;
|
|
8
9
|
static styles: import("lit").CSSResult;
|
|
9
10
|
private toggle;
|
|
10
11
|
private close;
|
|
11
12
|
private openFull;
|
|
12
13
|
connectedCallback(): void;
|
|
13
14
|
disconnectedCallback(): void;
|
|
15
|
+
protected willUpdate(changed: PropertyValues<this>): void;
|
|
16
|
+
private readonly onLanguageChanged;
|
|
17
|
+
private check;
|
|
14
18
|
/**
|
|
15
19
|
* `Escape` 로 닫는다 — **뒷막 누름의 키보드 짝**이다.
|
|
16
20
|
*
|
|
@@ -18,7 +22,7 @@ export declare class OxHelpSpot extends LitElement {
|
|
|
18
22
|
* 보이는 부류다(WCAG 2.1 §2.1.1 · §2.1.2).
|
|
19
23
|
*/
|
|
20
24
|
private readonly onKeydown;
|
|
21
|
-
render(): TemplateResult;
|
|
25
|
+
render(): TemplateResult | typeof nothing;
|
|
22
26
|
}
|
|
23
27
|
declare global {
|
|
24
28
|
interface HTMLElementTagNameMap {
|
|
@@ -1,7 +1,32 @@
|
|
|
1
1
|
import { __decorate } from "tslib";
|
|
2
|
-
import { LitElement, html, css } from 'lit';
|
|
2
|
+
import { LitElement, html, css, nothing } from 'lit';
|
|
3
3
|
import { customElement, property, state } from 'lit/decorators.js';
|
|
4
|
+
import { i18next } from '@operato/i18n';
|
|
4
5
|
import './ox-doc.js';
|
|
6
|
+
/*
|
|
7
|
+
* What each /helps/{topic}.html holds, read once per language. A spot asks before it shows its `?`:
|
|
8
|
+
* many spots on a page share one topic, and the answer does not change while the page is open.
|
|
9
|
+
*/
|
|
10
|
+
const documents = new Map();
|
|
11
|
+
function documentOf(topic, lang) {
|
|
12
|
+
const key = `${lang}:${topic}`;
|
|
13
|
+
if (!documents.has(key)) {
|
|
14
|
+
documents.set(key, fetch(`/helps/${topic}.html?lng=${encodeURIComponent(lang)}`, { headers: { 'accept-language': lang } })
|
|
15
|
+
.then(res => (res.ok ? res.text() : null))
|
|
16
|
+
.catch(() => null));
|
|
17
|
+
}
|
|
18
|
+
return documents.get(key);
|
|
19
|
+
}
|
|
20
|
+
/** The topic is written, and so is the section when one is named. */
|
|
21
|
+
function holds(content, section) {
|
|
22
|
+
if (!(content === null || content === void 0 ? void 0 : content.trim()))
|
|
23
|
+
return false;
|
|
24
|
+
if (!section)
|
|
25
|
+
return true;
|
|
26
|
+
const tpl = document.createElement('template');
|
|
27
|
+
tpl.innerHTML = content;
|
|
28
|
+
return !!tpl.content.querySelector(`#${CSS.escape(section)}`);
|
|
29
|
+
}
|
|
5
30
|
/*
|
|
6
31
|
* ox-help-spot — 인라인 문맥 도움말(C). 운영 화면 어디서든 `?` 하나로 관련 개념을 "그 자리에서" 연다.
|
|
7
32
|
* 같은 단일 소스(helps/{topic}.html)를 ox-doc 의 section 슬라이스 + compact 로 재사용 → 세 번째 소비 표면.
|
|
@@ -15,6 +40,14 @@ let OxHelpSpot = class OxHelpSpot extends LitElement {
|
|
|
15
40
|
this.section = '';
|
|
16
41
|
this.label = '';
|
|
17
42
|
this.open = false;
|
|
43
|
+
/*
|
|
44
|
+
* Whether there is anything to open. A `?` is placed where the concept is first read, often before
|
|
45
|
+
* anyone has written its help; until then it stays out of sight. Without this the button opened
|
|
46
|
+
* 「Content not found」, and a missing section opened the whole document instead of the part asked for.
|
|
47
|
+
*/
|
|
48
|
+
this.written = false;
|
|
49
|
+
/* Another language is another file, which may not be written yet. */
|
|
50
|
+
this.onLanguageChanged = () => this.check();
|
|
18
51
|
/**
|
|
19
52
|
* `Escape` 로 닫는다 — **뒷막 누름의 키보드 짝**이다.
|
|
20
53
|
*
|
|
@@ -38,14 +71,38 @@ let OxHelpSpot = class OxHelpSpot extends LitElement {
|
|
|
38
71
|
this.dispatchEvent(new CustomEvent('ox-help-open-full', { detail: { topic: this.topic, section: this.section }, bubbles: true, composed: true }));
|
|
39
72
|
}
|
|
40
73
|
connectedCallback() {
|
|
74
|
+
var _a;
|
|
41
75
|
super.connectedCallback();
|
|
42
76
|
this.addEventListener('keydown', this.onKeydown);
|
|
77
|
+
this.toggleAttribute('hidden', !this.written);
|
|
78
|
+
(_a = i18next === null || i18next === void 0 ? void 0 : i18next.on) === null || _a === void 0 ? void 0 : _a.call(i18next, 'languageChanged', this.onLanguageChanged);
|
|
43
79
|
}
|
|
44
80
|
disconnectedCallback() {
|
|
81
|
+
var _a;
|
|
45
82
|
this.removeEventListener('keydown', this.onKeydown);
|
|
83
|
+
(_a = i18next === null || i18next === void 0 ? void 0 : i18next.off) === null || _a === void 0 ? void 0 : _a.call(i18next, 'languageChanged', this.onLanguageChanged);
|
|
46
84
|
super.disconnectedCallback();
|
|
47
85
|
}
|
|
86
|
+
willUpdate(changed) {
|
|
87
|
+
if (changed.has('topic') || changed.has('section')) {
|
|
88
|
+
this.check();
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
async check() {
|
|
92
|
+
var _a;
|
|
93
|
+
const { topic, section } = this;
|
|
94
|
+
const lang = (_a = i18next === null || i18next === void 0 ? void 0 : i18next.language) !== null && _a !== void 0 ? _a : 'en';
|
|
95
|
+
const written = !!topic && holds(await documentOf(topic, lang), section);
|
|
96
|
+
if (topic === this.topic && section === this.section) {
|
|
97
|
+
this.written = written;
|
|
98
|
+
this.toggleAttribute('hidden', !written);
|
|
99
|
+
if (!written)
|
|
100
|
+
this.open = false;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
48
103
|
render() {
|
|
104
|
+
if (!this.written)
|
|
105
|
+
return nothing;
|
|
49
106
|
return html `
|
|
50
107
|
<button class="q" title=${this.label || 'help'} aria-label=${this.label || 'help'} @click=${(e) => this.toggle(e)}>?</button>
|
|
51
108
|
${this.open
|
|
@@ -72,6 +129,7 @@ let OxHelpSpot = class OxHelpSpot extends LitElement {
|
|
|
72
129
|
}
|
|
73
130
|
};
|
|
74
131
|
OxHelpSpot.styles = css `
|
|
132
|
+
:host([hidden]) { display: none; }
|
|
75
133
|
/* 중심 정렬은 담는 쪽 책임 — 제목 행을 flex(align-items:center)로 잡아 광학 중심에 맞춘다.
|
|
76
134
|
inline 으로 쓰일 때의 기본 정렬만 여기서 vertical-align:middle 로 둔다. */
|
|
77
135
|
:host { display: inline-flex; align-items: center; vertical-align: middle; --_accent: var(--ox-doc-accent, var(--ui-accent, #0d8f86)); }
|
|
@@ -101,6 +159,9 @@ __decorate([
|
|
|
101
159
|
__decorate([
|
|
102
160
|
state()
|
|
103
161
|
], OxHelpSpot.prototype, "open", void 0);
|
|
162
|
+
__decorate([
|
|
163
|
+
state()
|
|
164
|
+
], OxHelpSpot.prototype, "written", void 0);
|
|
104
165
|
OxHelpSpot = __decorate([
|
|
105
166
|
customElement('ox-help-spot')
|
|
106
167
|
], OxHelpSpot);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ox-help-spot.js","sourceRoot":"","sources":["../../../src/components/ox-help-spot.ts"],"names":[],"mappings":"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","sourcesContent":["import { LitElement, html, css, type TemplateResult } from 'lit'\nimport { customElement, property, state } from 'lit/decorators.js'\nimport './ox-doc.js'\n\n/*\n * ox-help-spot — 인라인 문맥 도움말(C). 운영 화면 어디서든 `?` 하나로 관련 개념을 \"그 자리에서\" 연다.\n * 같은 단일 소스(helps/{topic}.html)를 ox-doc 의 section 슬라이스 + compact 로 재사용 → 세 번째 소비 표면.\n * 사용: <ox-help-spot topic=\"twin/concept\" section=\"entities\"></ox-help-spot>\n * full 링크는 앱이 처리하도록 이벤트(ox-help-open-full) 방출.\n */\n@customElement('ox-help-spot')\nexport class OxHelpSpot extends LitElement {\n @property({ type: String }) topic = ''\n @property({ type: String }) section = ''\n @property({ type: String }) label = ''\n @state() private open = false\n\n static styles = css`\n /* 중심 정렬은 담는 쪽 책임 — 제목 행을 flex(align-items:center)로 잡아 광학 중심에 맞춘다.\n inline 으로 쓰일 때의 기본 정렬만 여기서 vertical-align:middle 로 둔다. */\n :host { display: inline-flex; align-items: center; vertical-align: middle; --_accent: var(--ox-doc-accent, var(--ui-accent, #0d8f86)); }\n button.q { width: 17px; height: 17px; border-radius: 50%; border: 1px solid color-mix(in oklab, var(--_accent), transparent 45%); background: color-mix(in oklab, var(--_accent), transparent 88%); color: var(--_accent); font: 600 11px/1 var(--ox-doc-sans, sans-serif); cursor: pointer; padding: 0; }\n button.q:hover { background: color-mix(in oklab, var(--_accent), transparent 75%); }\n .backdrop { position: fixed; inset: 0; background: rgba(10,15,20,.32); z-index: 9998; }\n .panel { position: fixed; z-index: 9999; top: 50%; left: 50%; transform: translate(-50%, -50%); width: min(620px, 92vw); max-height: 82vh; overflow: auto; background: var(--ox-doc-panel, var(--ui-panel, #fff)); border: 1px solid var(--ox-doc-line, var(--ui-line, #dde4ea)); border-radius: 14px; box-shadow: 0 20px 60px rgba(0,0,0,.28); padding: 8px 20px 18px; }\n /* 표준 스크롤바(얇은) — board-placement-tray 와 동일 컨벤션. */\n .panel { scrollbar-width: thin; scrollbar-color: var(--scrollbar-thumb-color, rgba(90, 107, 122, 0.4)) transparent; }\n .panel::-webkit-scrollbar { width: 6px; height: 6px; }\n .panel::-webkit-scrollbar-track { background: transparent; }\n .panel::-webkit-scrollbar-thumb { background: var(--scrollbar-thumb-color, rgba(90, 107, 122, 0.4)); border-radius: 4px; }\n .panel::-webkit-scrollbar-thumb:hover { background: var(--scrollbar-thumb-hover-color, rgba(90, 107, 122, 0.6)); }\n .bar { position: sticky; top: 0; display: flex; justify-content: flex-end; gap: 10px; padding: 8px 0 4px; }\n .bar a, .bar button { font: 600 12px/1 var(--ox-doc-mono, monospace); color: var(--ox-doc-accent, var(--ui-accent, #0d8f86)); background: none; border: 0; cursor: pointer; text-decoration: none; letter-spacing: .04em; }\n :focus-visible { outline: 2px solid var(--_accent); outline-offset: 2px; }\n `\n\n private toggle(e: Event) { e.stopPropagation(); this.open = !this.open }\n private close() { this.open = false }\n private openFull(e: Event) {\n e.preventDefault()\n this.close()\n this.dispatchEvent(new CustomEvent('ox-help-open-full', { detail: { topic: this.topic, section: this.section }, bubbles: true, composed: true }))\n }\n\n connectedCallback(): void {\n super.connectedCallback()\n this.addEventListener('keydown', this.onKeydown)\n }\n\n disconnectedCallback(): void {\n this.removeEventListener('keydown', this.onKeydown)\n super.disconnectedCallback()\n }\n\n /**\n * `Escape` 로 닫는다 — **뒷막 누름의 키보드 짝**이다.\n *\n * 이것이 없으면 도움말을 연 사람이 탭으로 그 안을 다 지나야 빠져나온다. 마우스로 열어 보면 안\n * 보이는 부류다(WCAG 2.1 §2.1.1 · §2.1.2).\n */\n private readonly onKeydown = (event: KeyboardEvent) => {\n if (!this.open || event.key !== 'Escape') {\n return\n }\n\n event.preventDefault()\n event.stopPropagation()\n this.close()\n }\n\n render(): TemplateResult {\n return html`\n <button class=\"q\" title=${this.label || 'help'} aria-label=${this.label || 'help'} @click=${(e: Event) => this.toggle(e)}>?</button>\n ${this.open\n ? html`\n ${/*\n * 뒷막은 **탭 순서에 넣지 않는다.** 눌러서 닫는 것은 마우스의 편의이고, 키보드로는\n * `Escape` 가 그 일을 한다(아래 `onKeydown`). 뒷막에 `tabindex` 를 주면 탭을 눌렀을 때\n * 아무것도 아닌 빈 면에 초점이 서고, 낭독기는 읽을 것이 없는 것을 읽는다.\n *\n * 그래서 보조기술에서 숨긴다 — 이 면은 그림이지 기능이 아니다.\n */ ''}\n <div class=\"backdrop\" aria-hidden=\"true\" @click=${() => this.close()}></div>\n ${/* 안쪽 누름이 뒷막까지 새지 않게만 한다 — 기능이 아니라 막는 것이다. */ ''}\n <div class=\"panel\" role=\"dialog\" aria-modal=\"true\" aria-label=${this.label || 'help'} tabindex=\"-1\" @click=${(e: Event) => e.stopPropagation()}>\n <div class=\"bar\">\n <a href=\"#\" @click=${(e: Event) => this.openFull(e)}>open full →</a>\n <button @click=${() => this.close()}>✕ close</button>\n </div>\n <ox-doc compact topic=${this.topic} section=${this.section}></ox-doc>\n </div>\n `\n : ''}\n `\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap { 'ox-help-spot': OxHelpSpot }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"ox-help-spot.js","sourceRoot":"","sources":["../../../src/components/ox-help-spot.ts"],"names":[],"mappings":"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","sourcesContent":["import { LitElement, html, css, nothing, type PropertyValues, type TemplateResult } from 'lit'\nimport { customElement, property, state } from 'lit/decorators.js'\nimport { i18next } from '@operato/i18n'\nimport './ox-doc.js'\n\n/*\n * What each /helps/{topic}.html holds, read once per language. A spot asks before it shows its `?`:\n * many spots on a page share one topic, and the answer does not change while the page is open.\n */\nconst documents = new Map<string, Promise<string | null>>()\n\nfunction documentOf(topic: string, lang: string): Promise<string | null> {\n const key = `${lang}:${topic}`\n if (!documents.has(key)) {\n documents.set(\n key,\n fetch(`/helps/${topic}.html?lng=${encodeURIComponent(lang)}`, { headers: { 'accept-language': lang } })\n .then(res => (res.ok ? res.text() : null))\n .catch(() => null)\n )\n }\n return documents.get(key)!\n}\n\n/** The topic is written, and so is the section when one is named. */\nfunction holds(content: string | null, section: string): boolean {\n if (!content?.trim()) return false\n if (!section) return true\n const tpl = document.createElement('template')\n tpl.innerHTML = content\n return !!tpl.content.querySelector(`#${CSS.escape(section)}`)\n}\n\n/*\n * ox-help-spot — 인라인 문맥 도움말(C). 운영 화면 어디서든 `?` 하나로 관련 개념을 \"그 자리에서\" 연다.\n * 같은 단일 소스(helps/{topic}.html)를 ox-doc 의 section 슬라이스 + compact 로 재사용 → 세 번째 소비 표면.\n * 사용: <ox-help-spot topic=\"twin/concept\" section=\"entities\"></ox-help-spot>\n * full 링크는 앱이 처리하도록 이벤트(ox-help-open-full) 방출.\n */\n@customElement('ox-help-spot')\nexport class OxHelpSpot extends LitElement {\n @property({ type: String }) topic = ''\n @property({ type: String }) section = ''\n @property({ type: String }) label = ''\n @state() private open = false\n /*\n * Whether there is anything to open. A `?` is placed where the concept is first read, often before\n * anyone has written its help; until then it stays out of sight. Without this the button opened\n * 「Content not found」, and a missing section opened the whole document instead of the part asked for.\n */\n @state() private written = false\n\n static styles = css`\n :host([hidden]) { display: none; }\n /* 중심 정렬은 담는 쪽 책임 — 제목 행을 flex(align-items:center)로 잡아 광학 중심에 맞춘다.\n inline 으로 쓰일 때의 기본 정렬만 여기서 vertical-align:middle 로 둔다. */\n :host { display: inline-flex; align-items: center; vertical-align: middle; --_accent: var(--ox-doc-accent, var(--ui-accent, #0d8f86)); }\n button.q { width: 17px; height: 17px; border-radius: 50%; border: 1px solid color-mix(in oklab, var(--_accent), transparent 45%); background: color-mix(in oklab, var(--_accent), transparent 88%); color: var(--_accent); font: 600 11px/1 var(--ox-doc-sans, sans-serif); cursor: pointer; padding: 0; }\n button.q:hover { background: color-mix(in oklab, var(--_accent), transparent 75%); }\n .backdrop { position: fixed; inset: 0; background: rgba(10,15,20,.32); z-index: 9998; }\n .panel { position: fixed; z-index: 9999; top: 50%; left: 50%; transform: translate(-50%, -50%); width: min(620px, 92vw); max-height: 82vh; overflow: auto; background: var(--ox-doc-panel, var(--ui-panel, #fff)); border: 1px solid var(--ox-doc-line, var(--ui-line, #dde4ea)); border-radius: 14px; box-shadow: 0 20px 60px rgba(0,0,0,.28); padding: 8px 20px 18px; }\n /* 표준 스크롤바(얇은) — board-placement-tray 와 동일 컨벤션. */\n .panel { scrollbar-width: thin; scrollbar-color: var(--scrollbar-thumb-color, rgba(90, 107, 122, 0.4)) transparent; }\n .panel::-webkit-scrollbar { width: 6px; height: 6px; }\n .panel::-webkit-scrollbar-track { background: transparent; }\n .panel::-webkit-scrollbar-thumb { background: var(--scrollbar-thumb-color, rgba(90, 107, 122, 0.4)); border-radius: 4px; }\n .panel::-webkit-scrollbar-thumb:hover { background: var(--scrollbar-thumb-hover-color, rgba(90, 107, 122, 0.6)); }\n .bar { position: sticky; top: 0; display: flex; justify-content: flex-end; gap: 10px; padding: 8px 0 4px; }\n .bar a, .bar button { font: 600 12px/1 var(--ox-doc-mono, monospace); color: var(--ox-doc-accent, var(--ui-accent, #0d8f86)); background: none; border: 0; cursor: pointer; text-decoration: none; letter-spacing: .04em; }\n :focus-visible { outline: 2px solid var(--_accent); outline-offset: 2px; }\n `\n\n private toggle(e: Event) { e.stopPropagation(); this.open = !this.open }\n private close() { this.open = false }\n private openFull(e: Event) {\n e.preventDefault()\n this.close()\n this.dispatchEvent(new CustomEvent('ox-help-open-full', { detail: { topic: this.topic, section: this.section }, bubbles: true, composed: true }))\n }\n\n connectedCallback(): void {\n super.connectedCallback()\n this.addEventListener('keydown', this.onKeydown)\n this.toggleAttribute('hidden', !this.written)\n i18next?.on?.('languageChanged', this.onLanguageChanged)\n }\n\n disconnectedCallback(): void {\n this.removeEventListener('keydown', this.onKeydown)\n i18next?.off?.('languageChanged', this.onLanguageChanged)\n super.disconnectedCallback()\n }\n\n protected willUpdate(changed: PropertyValues<this>): void {\n if (changed.has('topic') || changed.has('section')) {\n this.check()\n }\n }\n\n /* Another language is another file, which may not be written yet. */\n private readonly onLanguageChanged = () => this.check()\n\n private async check(): Promise<void> {\n const { topic, section } = this\n const lang = i18next?.language ?? 'en'\n const written = !!topic && holds(await documentOf(topic, lang), section)\n if (topic === this.topic && section === this.section) {\n this.written = written\n this.toggleAttribute('hidden', !written)\n if (!written) this.open = false\n }\n }\n\n /**\n * `Escape` 로 닫는다 — **뒷막 누름의 키보드 짝**이다.\n *\n * 이것이 없으면 도움말을 연 사람이 탭으로 그 안을 다 지나야 빠져나온다. 마우스로 열어 보면 안\n * 보이는 부류다(WCAG 2.1 §2.1.1 · §2.1.2).\n */\n private readonly onKeydown = (event: KeyboardEvent) => {\n if (!this.open || event.key !== 'Escape') {\n return\n }\n\n event.preventDefault()\n event.stopPropagation()\n this.close()\n }\n\n render(): TemplateResult | typeof nothing {\n if (!this.written) return nothing\n return html`\n <button class=\"q\" title=${this.label || 'help'} aria-label=${this.label || 'help'} @click=${(e: Event) => this.toggle(e)}>?</button>\n ${this.open\n ? html`\n ${/*\n * 뒷막은 **탭 순서에 넣지 않는다.** 눌러서 닫는 것은 마우스의 편의이고, 키보드로는\n * `Escape` 가 그 일을 한다(아래 `onKeydown`). 뒷막에 `tabindex` 를 주면 탭을 눌렀을 때\n * 아무것도 아닌 빈 면에 초점이 서고, 낭독기는 읽을 것이 없는 것을 읽는다.\n *\n * 그래서 보조기술에서 숨긴다 — 이 면은 그림이지 기능이 아니다.\n */ ''}\n <div class=\"backdrop\" aria-hidden=\"true\" @click=${() => this.close()}></div>\n ${/* 안쪽 누름이 뒷막까지 새지 않게만 한다 — 기능이 아니라 막는 것이다. */ ''}\n <div class=\"panel\" role=\"dialog\" aria-modal=\"true\" aria-label=${this.label || 'help'} tabindex=\"-1\" @click=${(e: Event) => e.stopPropagation()}>\n <div class=\"bar\">\n <a href=\"#\" @click=${(e: Event) => this.openFull(e)}>open full →</a>\n <button @click=${() => this.close()}>✕ close</button>\n </div>\n <ox-doc compact topic=${this.topic} section=${this.section}></ox-doc>\n </div>\n `\n : ''}\n `\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap { 'ox-help-spot': OxHelpSpot }\n}\n"]}
|
package/dist/test/ox-doc.test.js
CHANGED
|
@@ -62,6 +62,7 @@ describe('ox-doc', () => {
|
|
|
62
62
|
describe('ox-help-spot', () => {
|
|
63
63
|
it('opens the section in place and closes on Escape', async () => {
|
|
64
64
|
const el = await fixture(html `<ox-help-spot topic="a/one" section="part"></ox-help-spot>`);
|
|
65
|
+
await waitUntil(() => el.shadowRoot.querySelector('button.q'));
|
|
65
66
|
el.shadowRoot.querySelector('button.q').click();
|
|
66
67
|
await waitUntil(() => el.shadowRoot.querySelector('ox-doc'));
|
|
67
68
|
/* The dialog says what opened and can take focus (operato-twin's overlay guard asks both). */
|
|
@@ -74,5 +75,22 @@ describe('ox-help-spot', () => {
|
|
|
74
75
|
el.shadowRoot.querySelector('.panel').dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape', bubbles: true, composed: true }));
|
|
75
76
|
await waitUntil(() => !el.shadowRoot.querySelector('.panel'));
|
|
76
77
|
});
|
|
78
|
+
/* A `?` is placed before its help is written; it must not open 「Content not found」 or the whole document. */
|
|
79
|
+
it('stays out of sight until its topic is written', async () => {
|
|
80
|
+
const el = await fixture(html `<ox-help-spot topic="a/none" section="part"></ox-help-spot>`);
|
|
81
|
+
await waitUntil(() => requests.some(url => url.startsWith('/helps/a/none.html')));
|
|
82
|
+
await new Promise(resolve => setTimeout(resolve, 20));
|
|
83
|
+
expect(!!el.shadowRoot.querySelector('button.q')).to.equal(false);
|
|
84
|
+
expect(el.hasAttribute('hidden')).to.equal(true);
|
|
85
|
+
expect(getComputedStyle(el).display).to.equal('none');
|
|
86
|
+
});
|
|
87
|
+
it('stays out of sight while the named section is not written', async () => {
|
|
88
|
+
const shown = await fixture(html `<ox-help-spot topic="a/one" section="part"></ox-help-spot>`);
|
|
89
|
+
const missing = await fixture(html `<ox-help-spot topic="a/one" section="later"></ox-help-spot>`);
|
|
90
|
+
await waitUntil(() => shown.shadowRoot.querySelector('button.q'));
|
|
91
|
+
await new Promise(resolve => setTimeout(resolve, 20));
|
|
92
|
+
expect(!!missing.shadowRoot.querySelector('button.q')).to.equal(false);
|
|
93
|
+
expect(missing.hasAttribute('hidden')).to.equal(true);
|
|
94
|
+
});
|
|
77
95
|
});
|
|
78
96
|
//# sourceMappingURL=ox-doc.test.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ox-doc.test.js","sourceRoot":"","sources":["../../test/ox-doc.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { expect, fixture, html, waitUntil } from '@open-wc/testing'\n\nimport '../src/components/ox-doc.js'\nimport '../src/components/ox-help-spot.js'\nimport type { OxDoc } from '../src/components/ox-doc.js'\n\n/**\n * ox-doc reads /helps/{topic}.html and renders it; the products that used to keep their own copy rely on\n * these behaviours.\n */\nconst DOCS: Record<string, string> = {\n 'a/one': `<h1 id=\"top\">One</h1><p>lead</p><h2 id=\"part\">Part</h2><p>in part</p><table><tr><td>x</td></tr></table><h2 id=\"next\">Next</h2><p>after</p><a data-topic=\"a/two#end\">two</a>`,\n 'a/two': `<h1>Two</h1><h2 id=\"end\">End</h2>`\n}\n\nlet requests: string[] = []\nconst realFetch = window.fetch\n\nbeforeEach(() => {\n requests = []\n window.fetch = (async (input: RequestInfo | URL) => {\n const url = String(input)\n requests.push(url)\n const topic = url.replace(/^\\/helps\\//, '').replace(/\\.html.*$/, '')\n return DOCS[topic] ? new Response(DOCS[topic]) : new Response('', { status: 404 })\n }) as typeof window.fetch\n})\n\nafterEach(() => {\n window.fetch = realFetch\n})\n\nconst bodyOf = (el: OxDoc) => el.shadowRoot!.querySelector('.body') as HTMLElement\n\ndescribe('ox-doc', () => {\n it('reads /helps/{topic}.html with the language and renders it', async () => {\n const el = await fixture<OxDoc>(html`<ox-doc topic=\"a/one\"></ox-doc>`)\n await waitUntil(() => bodyOf(el)?.querySelector('h1'))\n\n expect(requests[0]).to.match(/^\\/helps\\/a\\/one\\.html\\?lng=/)\n expect(bodyOf(el).querySelector('h1')!.textContent).to.equal('One')\n })\n\n it('wraps tables in a scrolling box so they do not push the page wide', async () => {\n const el = await fixture<OxDoc>(html`<ox-doc topic=\"a/one\"></ox-doc>`)\n await waitUntil(() => bodyOf(el)?.querySelector('table'))\n\n expect(bodyOf(el).querySelector('table')!.parentElement!.classList.contains('tablewrap')).to.equal(true)\n })\n\n it('shows only the named section, up to the next heading of its level', async () => {\n const el = await fixture<OxDoc>(html`<ox-doc topic=\"a/one\" section=\"part\"></ox-doc>`)\n await waitUntil(() => bodyOf(el)?.querySelector('h2'))\n\n expect(bodyOf(el).textContent).to.contain('in part')\n expect(bodyOf(el).textContent).to.not.contain('lead')\n expect(bodyOf(el).textContent).to.not.contain('after')\n })\n\n it('follows a topic link in place and tells the host, without keeping the anchor in the file name', async () => {\n const el = await fixture<OxDoc>(html`<ox-doc topic=\"a/one\"></ox-doc>`)\n await waitUntil(() => bodyOf(el)?.querySelector('a[data-topic]'))\n\n let told: any\n el.addEventListener('ox-doc-topic', e => (told = (e as CustomEvent).detail))\n ;(bodyOf(el).querySelector('a[data-topic]') as HTMLElement).click()\n await waitUntil(() => bodyOf(el)?.querySelector('h1')?.textContent === 'Two')\n\n expect(told).to.deep.equal({ topic: 'a/two', hash: 'end' })\n expect(requests[requests.length - 1]).to.match(/^\\/helps\\/a\\/two\\.html\\?/)\n })\n\n it('says the topic is missing instead of showing an empty page', async () => {\n const el = await fixture<OxDoc>(html`<ox-doc topic=\"a/none\"></ox-doc>`)\n await waitUntil(() => el.shadowRoot!.querySelector('.status.err'))\n\n expect(el.shadowRoot!.querySelector('.status.err')!.textContent).to.contain('a/none')\n })\n})\n\ndescribe('ox-help-spot', () => {\n it('opens the section in place and closes on Escape', async () => {\n const el = await fixture<HTMLElement>(html`<ox-help-spot topic=\"a/one\" section=\"part\"></ox-help-spot>`)\n ;(el.shadowRoot!.querySelector('button.q') as HTMLButtonElement).click()\n await waitUntil(() => el.shadowRoot!.querySelector('ox-doc'))\n\n /* The dialog says what opened and can take focus (operato-twin's overlay guard asks both). */\n const panel = el.shadowRoot!.querySelector('.panel')!\n expect(panel.getAttribute('aria-label')).to.equal('help')\n expect(panel.getAttribute('tabindex')).to.equal('-1')\n\n const doc = el.shadowRoot!.querySelector('ox-doc') as OxDoc\n expect(doc.getAttribute('section')).to.equal('part')\n expect(doc.hasAttribute('compact')).to.equal(true)\n\n el.shadowRoot!.querySelector('.panel')!.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape', bubbles: true, composed: true }))\n await waitUntil(() => !el.shadowRoot!.querySelector('.panel'))\n })\n})\n"]}
|
|
1
|
+
{"version":3,"file":"ox-doc.test.js","sourceRoot":"","sources":["../../test/ox-doc.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { expect, fixture, html, waitUntil } from '@open-wc/testing'\n\nimport '../src/components/ox-doc.js'\nimport '../src/components/ox-help-spot.js'\nimport type { OxDoc } from '../src/components/ox-doc.js'\n\n/**\n * ox-doc reads /helps/{topic}.html and renders it; the products that used to keep their own copy rely on\n * these behaviours.\n */\nconst DOCS: Record<string, string> = {\n 'a/one': `<h1 id=\"top\">One</h1><p>lead</p><h2 id=\"part\">Part</h2><p>in part</p><table><tr><td>x</td></tr></table><h2 id=\"next\">Next</h2><p>after</p><a data-topic=\"a/two#end\">two</a>`,\n 'a/two': `<h1>Two</h1><h2 id=\"end\">End</h2>`\n}\n\nlet requests: string[] = []\nconst realFetch = window.fetch\n\nbeforeEach(() => {\n requests = []\n window.fetch = (async (input: RequestInfo | URL) => {\n const url = String(input)\n requests.push(url)\n const topic = url.replace(/^\\/helps\\//, '').replace(/\\.html.*$/, '')\n return DOCS[topic] ? new Response(DOCS[topic]) : new Response('', { status: 404 })\n }) as typeof window.fetch\n})\n\nafterEach(() => {\n window.fetch = realFetch\n})\n\nconst bodyOf = (el: OxDoc) => el.shadowRoot!.querySelector('.body') as HTMLElement\n\ndescribe('ox-doc', () => {\n it('reads /helps/{topic}.html with the language and renders it', async () => {\n const el = await fixture<OxDoc>(html`<ox-doc topic=\"a/one\"></ox-doc>`)\n await waitUntil(() => bodyOf(el)?.querySelector('h1'))\n\n expect(requests[0]).to.match(/^\\/helps\\/a\\/one\\.html\\?lng=/)\n expect(bodyOf(el).querySelector('h1')!.textContent).to.equal('One')\n })\n\n it('wraps tables in a scrolling box so they do not push the page wide', async () => {\n const el = await fixture<OxDoc>(html`<ox-doc topic=\"a/one\"></ox-doc>`)\n await waitUntil(() => bodyOf(el)?.querySelector('table'))\n\n expect(bodyOf(el).querySelector('table')!.parentElement!.classList.contains('tablewrap')).to.equal(true)\n })\n\n it('shows only the named section, up to the next heading of its level', async () => {\n const el = await fixture<OxDoc>(html`<ox-doc topic=\"a/one\" section=\"part\"></ox-doc>`)\n await waitUntil(() => bodyOf(el)?.querySelector('h2'))\n\n expect(bodyOf(el).textContent).to.contain('in part')\n expect(bodyOf(el).textContent).to.not.contain('lead')\n expect(bodyOf(el).textContent).to.not.contain('after')\n })\n\n it('follows a topic link in place and tells the host, without keeping the anchor in the file name', async () => {\n const el = await fixture<OxDoc>(html`<ox-doc topic=\"a/one\"></ox-doc>`)\n await waitUntil(() => bodyOf(el)?.querySelector('a[data-topic]'))\n\n let told: any\n el.addEventListener('ox-doc-topic', e => (told = (e as CustomEvent).detail))\n ;(bodyOf(el).querySelector('a[data-topic]') as HTMLElement).click()\n await waitUntil(() => bodyOf(el)?.querySelector('h1')?.textContent === 'Two')\n\n expect(told).to.deep.equal({ topic: 'a/two', hash: 'end' })\n expect(requests[requests.length - 1]).to.match(/^\\/helps\\/a\\/two\\.html\\?/)\n })\n\n it('says the topic is missing instead of showing an empty page', async () => {\n const el = await fixture<OxDoc>(html`<ox-doc topic=\"a/none\"></ox-doc>`)\n await waitUntil(() => el.shadowRoot!.querySelector('.status.err'))\n\n expect(el.shadowRoot!.querySelector('.status.err')!.textContent).to.contain('a/none')\n })\n})\n\ndescribe('ox-help-spot', () => {\n it('opens the section in place and closes on Escape', async () => {\n const el = await fixture<HTMLElement>(html`<ox-help-spot topic=\"a/one\" section=\"part\"></ox-help-spot>`)\n await waitUntil(() => el.shadowRoot!.querySelector('button.q'))\n ;(el.shadowRoot!.querySelector('button.q') as HTMLButtonElement).click()\n await waitUntil(() => el.shadowRoot!.querySelector('ox-doc'))\n\n /* The dialog says what opened and can take focus (operato-twin's overlay guard asks both). */\n const panel = el.shadowRoot!.querySelector('.panel')!\n expect(panel.getAttribute('aria-label')).to.equal('help')\n expect(panel.getAttribute('tabindex')).to.equal('-1')\n\n const doc = el.shadowRoot!.querySelector('ox-doc') as OxDoc\n expect(doc.getAttribute('section')).to.equal('part')\n expect(doc.hasAttribute('compact')).to.equal(true)\n\n el.shadowRoot!.querySelector('.panel')!.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape', bubbles: true, composed: true }))\n await waitUntil(() => !el.shadowRoot!.querySelector('.panel'))\n })\n\n /* A `?` is placed before its help is written; it must not open 「Content not found」 or the whole document. */\n it('stays out of sight until its topic is written', async () => {\n const el = await fixture<HTMLElement>(html`<ox-help-spot topic=\"a/none\" section=\"part\"></ox-help-spot>`)\n await waitUntil(() => requests.some(url => url.startsWith('/helps/a/none.html')))\n await new Promise(resolve => setTimeout(resolve, 20))\n\n expect(!!el.shadowRoot!.querySelector('button.q')).to.equal(false)\n expect(el.hasAttribute('hidden')).to.equal(true)\n expect(getComputedStyle(el).display).to.equal('none')\n })\n\n it('stays out of sight while the named section is not written', async () => {\n const shown = await fixture<HTMLElement>(html`<ox-help-spot topic=\"a/one\" section=\"part\"></ox-help-spot>`)\n const missing = await fixture<HTMLElement>(html`<ox-help-spot topic=\"a/one\" section=\"later\"></ox-help-spot>`)\n await waitUntil(() => shown.shadowRoot!.querySelector('button.q'))\n await new Promise(resolve => setTimeout(resolve, 20))\n\n expect(!!missing.shadowRoot!.querySelector('button.q')).to.equal(false)\n expect(missing.hasAttribute('hidden')).to.equal(true)\n })\n})\n"]}
|