help-layer 1.1.0 → 1.2.0
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/README.ja.md +62 -3
- package/README.md +65 -3
- package/dist/help-layer.esm.js +3 -3
- package/dist/help-layer.esm.js.map +4 -4
- package/dist/help-layer.iife.js +3 -3
- package/dist/help-layer.iife.js.map +4 -4
- package/dist/types/aria-isolation.d.ts +10 -0
- package/dist/types/floating.d.ts +9 -1
- package/dist/types/markers.d.ts +4 -1
- package/package.json +5 -4
- package/src/aria-isolation.js +67 -0
- package/src/dom-builder.js +12 -3
- package/src/floating.js +44 -1
- package/src/markers.js +14 -2
- package/src/toggle.js +19 -1
package/README.ja.md
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
[](https://www.npmjs.com/package/help-layer)
|
|
4
4
|
[](./LICENSE)
|
|
5
5
|
[](https://github.com/Y1-Effy/HelpLayer)
|
|
6
|
+
[](https://github.com/Y1-Effy/HelpLayer/actions/workflows/ci.yml)
|
|
6
7
|
|
|
7
8
|
[English](./README.md) | **日本語**
|
|
8
9
|
|
|
@@ -16,7 +17,7 @@
|
|
|
16
17
|
|
|
17
18
|
- 依存は [`@floating-ui/dom`](https://floating-ui.com/) のみ・軽量(プリビルドの IIFE で約 33KB minified、`@floating-ui/dom` 同梱)
|
|
18
19
|
- Shadow DOM 貫通・SPA の動的要素・マーカー同士の重なり回避・画面端でのポップアップ自動調整に対応
|
|
19
|
-
- キーボード操作・スクリーンリーダーに配慮(ポップアップは `role="dialog"`、開くとフォーカスを移し閉じるとマーカーへ復帰。モード中はフォーカスを UI 内に封じ込め、`Esc`
|
|
20
|
+
- キーボード操作・スクリーンリーダーに配慮(ポップアップは `role="dialog"`、開くとフォーカスを移し閉じるとマーカーへ復帰。モード中はフォーカスを UI 内に封じ込め、`Esc` で説明を閉じる(開いている説明が無ければ解説モード自体を終了))
|
|
20
21
|
- ON→OFF で追加した DOM・イベント・スタイルを**完全後始末**
|
|
21
22
|
- モダンブラウザ(Chromium / Firefox / WebKit)で動作(e2e を 3 エンジンで検証)
|
|
22
23
|
|
|
@@ -29,7 +30,9 @@
|
|
|
29
30
|
- [自由配置(要素に紐づけない説明)](#自由配置要素に紐づけない説明)
|
|
30
31
|
- [API](#api)
|
|
31
32
|
- [テーマ(CSS カスタムプロパティ)](#テーマcss-カスタムプロパティ)
|
|
33
|
+
- [対応環境(ブラウザ/ランタイム)](#対応環境ブラウザランタイム)
|
|
32
34
|
- [既知の制約](#既知の制約)
|
|
35
|
+
- [アクセシビリティ](#アクセシビリティ)
|
|
33
36
|
- [セキュリティ](#セキュリティ)
|
|
34
37
|
- [開発](#開発)
|
|
35
38
|
|
|
@@ -137,7 +140,7 @@ CDN から読む場合は、改ざん検知のため **バージョンを固定*
|
|
|
137
140
|
|
|
138
141
|
```html
|
|
139
142
|
<script
|
|
140
|
-
src="https://unpkg.com/help-layer@1.0
|
|
143
|
+
src="https://unpkg.com/help-layer@1.2.0/dist/help-layer.iife.js"
|
|
141
144
|
integrity="sha384-……(公開版のハッシュに差し替え)"
|
|
142
145
|
crossorigin="anonymous"></script>
|
|
143
146
|
<script>
|
|
@@ -149,7 +152,7 @@ CDN から読む場合は、改ざん検知のため **バージョンを固定*
|
|
|
149
152
|
```
|
|
150
153
|
|
|
151
154
|
> `integrity` のハッシュは公開した実ファイルから生成します。例:
|
|
152
|
-
> `curl -s https://unpkg.com/help-layer@1.0
|
|
155
|
+
> `curl -s https://unpkg.com/help-layer@1.2.0/dist/help-layer.iife.js | openssl dgst -sha384 -binary | openssl base64 -A`
|
|
153
156
|
> (バージョンを固定しないと SRI と不整合になり読み込みが拒否されます。)
|
|
154
157
|
|
|
155
158
|
## 自由配置(要素に紐づけない説明)
|
|
@@ -244,14 +247,70 @@ initHelpLayer({
|
|
|
244
247
|
| `--help-layer-overlay-bg` | `transparent` | 遮断レイヤー(スクリム)背景色。`rgba(0,0,0,0.15)` 等で操作不能状態を可視化 |
|
|
245
248
|
| `--help-layer-overlay-cursor` | `default` | 遮断領域上のカーソル。`not-allowed` / `help` 等 |
|
|
246
249
|
|
|
250
|
+
## 対応環境(ブラウザ/ランタイム)
|
|
251
|
+
|
|
252
|
+
HelpLayer は **現代的な evergreen ブラウザ**(Chrome / Edge・Firefox・Safari)と近年の Electron の
|
|
253
|
+
Chromium を対象とします。**IE11 は非対応であり、構造上対応できません** — ES2020 構文・ES Modules・
|
|
254
|
+
`ResizeObserver`・Shadow DOM・`clip-path` に依存しており、IE はいずれも備えていないためです。これは
|
|
255
|
+
パッケージ形式の調整では埋められません。本当に古いランタイムを対象とする必要がある場合、本ライブラリは
|
|
256
|
+
適合しません。
|
|
257
|
+
|
|
258
|
+
下限を決める要素(新しい2つの API は縮退するため、実質的なハード下限はおよそ **2020 年代の evergreen**):
|
|
259
|
+
|
|
260
|
+
| 機能 | 用途 | 下限の目安 | フォールバック |
|
|
261
|
+
|---|---|---|---|
|
|
262
|
+
| ES2020+ES Modules | ライブラリ全体 | evergreen(〜2020) | なし(古い対象はトランスパイル/バンドルが必要) |
|
|
263
|
+
| `ResizeObserver`(`@floating-ui/dom` 経由) | マーカー/ポップアップの自動配置 | evergreen(〜2020) | なし |
|
|
264
|
+
| open Shadow DOM 貫通 | shadow root 内の対象探索 | evergreen | closed は設計上非対応 |
|
|
265
|
+
| `clip-path: polygon()` | 遮断レイヤーのトグル「穴」 | evergreen(極端に古い Safari は `-webkit-` 必要) | なし |
|
|
266
|
+
| `inert` | ホストを a11y ツリーから除外 | Chrome102 / FF112 / Safari15.5(2023) | 視覚+キーボード遮断のみに縮退 |
|
|
267
|
+
| `Element.checkVisibility()` | 対象が隠れた時にマーカーも隠す | Chrome105 / FF125 / Safari17.4(2024) | 0×0 rect 判定に縮退(`display:none` のみ検出) |
|
|
268
|
+
|
|
269
|
+
### モジュール形式
|
|
270
|
+
|
|
271
|
+
- **ESM(既定)**: `import { initHelpLayer } from 'help-layer'` はビルド済み・テスト済みの
|
|
272
|
+
`dist/help-layer.esm.js` を解決します(`@floating-ui/dom` は external のままで、バンドラ/npm が解決)。
|
|
273
|
+
- **バンドラ無し/`<script>`/CDN/厳格環境**: `@floating-ui/dom` を同梱しグローバル `HelpLayer` を公開する
|
|
274
|
+
自己完結の IIFE ビルドを使用 — [`<script>` だけで使う](#script-だけで使うバンドラなし) 参照。
|
|
275
|
+
- **CommonJS(`require`)**: `require` 入口は提供しません。ブラウザ DOM 専用のため Node の CJS 文脈では
|
|
276
|
+
意味を持ちません。非 ESM のツールチェーンではバンドラ経由で ESM を取り込むか、上記 IIFE を読み込んでください。
|
|
277
|
+
|
|
247
278
|
## 既知の制約
|
|
248
279
|
|
|
249
280
|
- closed な Shadow DOM は JS から到達できないため非対応(open のみ貫通)。
|
|
250
281
|
- マーカーを隅へ重ねるオフセットは既定マーカーサイズ(22px)前提。`--help-layer-marker-size` を大きく変えると
|
|
251
282
|
わずかにズレることがあります。
|
|
283
|
+
- **対象要素の「状態」変化は監視しません**(監視するのは「レイアウト」と「DOM 上の有無」のみ)。ON 中、
|
|
284
|
+
マーカーは対象の位置・サイズ変化に追従し、DOM への追加/削除に応じて mount/unmount され、対象自体が
|
|
285
|
+
隠れる/現れる(`display:none` 等)とマーカーも隠れる/戻ります。一方で、対象の**属性・内容の変化**は
|
|
286
|
+
検知しません — 既存要素への `data-help-id` の後付け/除去、`data-help-title` `data-help-text` の書き換え、
|
|
287
|
+
`disabled` 等の状態切り替えはマーカーに反映されません。これは意図的な制約です。文書全体に属性監視
|
|
288
|
+
(`MutationObserver` の `attributes: true, subtree: true`)を張ると、あらゆるクラス/スタイル変更で発火し、
|
|
289
|
+
ドロップイン型ライブラリとしては性能上のアンチパターンになるためです。これらの状態を変えた場合は、
|
|
290
|
+
`update(config)` で作り直す、モードを OFF→ON する、または対象要素を一度 DOM から外して入れ直してください
|
|
291
|
+
(入れ直しは `childList` 監視に乗ります)。
|
|
292
|
+
|
|
293
|
+
## アクセシビリティ
|
|
294
|
+
|
|
295
|
+
モード ON 中は、ホストアプリを視覚・ポインタ/キーボードだけでなく **支援技術(AT)に対しても意味的に
|
|
296
|
+
無効化**します。ホストを [`inert`](https://developer.mozilla.org/ja/docs/Web/HTML/Global_attributes/inert)
|
|
297
|
+
属性で a11y ツリーから除外するため、スクリーンリーダーの仮想カーソル(ブラウズモード)でも背景の読み上げ・
|
|
298
|
+
操作ができません。到達できるのはヘルプマーカー・ポップアップ・トグルのみです。ポップアップは
|
|
299
|
+
`aria-modal="true"` を持つ `role="dialog"` で、開くとフォーカスが移り、閉じるとマーカーへ戻ります。
|
|
300
|
+
|
|
301
|
+
この隔離の限定事項:
|
|
302
|
+
|
|
303
|
+
- `inert` は inert なサブツリーの子孫で打ち消せないため、隔離は document body の直下レベルで適用します
|
|
304
|
+
(トグルを透過させる clip-path の"穴"と同じ考え方)。トグルは操作可能である必要があるので、
|
|
305
|
+
**トグルを含む body 直下ブランチは到達可能なまま残します** — 漏れを最小化するにはトグルを body 直下
|
|
306
|
+
(または近い位置)に置いてください(直下の `<body>` 子要素なら漏れゼロ)。
|
|
307
|
+
- `inert` は現行ブラウザで広くサポートされます。非対応環境でも視覚/キーボードの遮断は有効で、AT 除外だけが
|
|
308
|
+
グレースフルに縮退します(エラーにはなりません)。
|
|
252
309
|
|
|
253
310
|
## セキュリティ
|
|
254
311
|
|
|
312
|
+
脆弱性の報告方法・サポート方針・セキュリティリリース方針は [SECURITY.ja.md](./SECURITY.ja.md) を参照してください。報告は公開 issue ではなく GitHub の非公開脆弱性報告をご利用ください。
|
|
313
|
+
|
|
255
314
|
- 設計上、`title` / `text` の描画は `textContent` のみで、`innerHTML` / `eval` / `new Function` は**一切使いません**。
|
|
256
315
|
- 外部通信(`fetch` 等)・`localStorage` / `cookie` などのストレージ利用も**ありません**(完全ローカル動作)。
|
|
257
316
|
- 唯一、未信頼データを HTML/DOM ノードとして挿入しうる経路は `render` オプションです。戻り値はサニタイズされないため、
|
package/README.md
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
[](https://www.npmjs.com/package/help-layer)
|
|
4
4
|
[](./LICENSE)
|
|
5
5
|
[](https://github.com/Y1-Effy/HelpLayer)
|
|
6
|
+
[](https://github.com/Y1-Effy/HelpLayer/actions/workflows/ci.yml)
|
|
6
7
|
|
|
7
8
|
**English** | [日本語](./README.ja.md)
|
|
8
9
|
|
|
@@ -16,7 +17,7 @@ It never touches the host app's own event listeners — a transparent blocking l
|
|
|
16
17
|
|
|
17
18
|
- Only one dependency, [`@floating-ui/dom`](https://floating-ui.com/); lightweight (the prebuilt IIFE is ~33KB minified, with `@floating-ui/dom` bundled in)
|
|
18
19
|
- Pierces Shadow DOM, keeps up with dynamically added/removed elements in SPAs, avoids marker-to-marker overlap, and auto-adjusts the popup at screen edges
|
|
19
|
-
- Mindful of keyboard use and screen readers (the popup is `role="dialog"`; opening moves focus and closing returns it to the marker; while the mode is on, focus is trapped within the UI, and `Esc` closes
|
|
20
|
+
- Mindful of keyboard use and screen readers (the popup is `role="dialog"`; opening moves focus and closing returns it to the marker; while the mode is on, focus is trapped within the UI, and `Esc` closes the popup — or exits the mode when no popup is open)
|
|
20
21
|
- Fully cleans up the DOM, listeners, and styles it added when you turn it OFF
|
|
21
22
|
- Works in modern browsers (Chromium / Firefox / WebKit; e2e is verified across all three engines)
|
|
22
23
|
|
|
@@ -29,7 +30,9 @@ It never touches the host app's own event listeners — a transparent blocking l
|
|
|
29
30
|
- [Free placement (descriptions not bound to an element)](#free-placement-descriptions-not-bound-to-an-element)
|
|
30
31
|
- [API](#api)
|
|
31
32
|
- [Theming (CSS custom properties)](#theming-css-custom-properties)
|
|
33
|
+
- [Browser & runtime support](#browser--runtime-support)
|
|
32
34
|
- [Known limitations](#known-limitations)
|
|
35
|
+
- [Accessibility](#accessibility)
|
|
33
36
|
- [Security](#security)
|
|
34
37
|
- [Development](#development)
|
|
35
38
|
|
|
@@ -143,7 +146,7 @@ When loading from a CDN, we recommend **pinning the version** and adding **SRI (
|
|
|
143
146
|
|
|
144
147
|
```html
|
|
145
148
|
<script
|
|
146
|
-
src="https://unpkg.com/help-layer@1.0
|
|
149
|
+
src="https://unpkg.com/help-layer@1.2.0/dist/help-layer.iife.js"
|
|
147
150
|
integrity="sha384-……(replace with the published file's hash)"
|
|
148
151
|
crossorigin="anonymous"></script>
|
|
149
152
|
<script>
|
|
@@ -155,7 +158,7 @@ When loading from a CDN, we recommend **pinning the version** and adding **SRI (
|
|
|
155
158
|
```
|
|
156
159
|
|
|
157
160
|
> Generate the `integrity` hash from the actually published file, e.g.:
|
|
158
|
-
> `curl -s https://unpkg.com/help-layer@1.0
|
|
161
|
+
> `curl -s https://unpkg.com/help-layer@1.2.0/dist/help-layer.iife.js | openssl dgst -sha384 -binary | openssl base64 -A`
|
|
159
162
|
> (If you don't pin the version, the SRI will mismatch and the browser will refuse to load it.)
|
|
160
163
|
|
|
161
164
|
## Free placement (descriptions not bound to an element)
|
|
@@ -250,14 +253,73 @@ You can change the look just by overriding the following variables in your host
|
|
|
250
253
|
| `--help-layer-overlay-bg` | `transparent` | blocking-layer (scrim) background; e.g. `rgba(0,0,0,0.15)` to signal the host is inactive |
|
|
251
254
|
| `--help-layer-overlay-cursor` | `default` | cursor over the blocked area; e.g. `not-allowed` / `help` |
|
|
252
255
|
|
|
256
|
+
## Browser & runtime support
|
|
257
|
+
|
|
258
|
+
HelpLayer targets **modern evergreen browsers** (Chrome / Edge, Firefox, Safari) and the Chromium in
|
|
259
|
+
recent Electron. **Internet Explorer 11 is not supported and cannot be** — the library relies on ES2020
|
|
260
|
+
syntax, ES modules, `ResizeObserver`, Shadow DOM, and `clip-path`, none of which IE provides. Packaging
|
|
261
|
+
changes can't bridge this; if you must support genuinely old runtimes, this library is not the right fit.
|
|
262
|
+
|
|
263
|
+
What sets the minimum (the two newest APIs degrade gracefully, so the practical hard floor is roughly
|
|
264
|
+
**2020-era evergreen**):
|
|
265
|
+
|
|
266
|
+
| Feature | Used for | Minimum | Fallback |
|
|
267
|
+
|---|---|---|---|
|
|
268
|
+
| ES2020 + ES modules | the whole library | evergreen (~2020) | none — transpile/bundle for older targets |
|
|
269
|
+
| `ResizeObserver` (via `@floating-ui/dom`) | marker/popup auto-positioning | evergreen (~2020) | none |
|
|
270
|
+
| Open Shadow DOM piercing | finding targets inside shadow roots | evergreen | closed shadow roots are unsupported by design |
|
|
271
|
+
| `clip-path: polygon()` | the blocking layer's toggle "hole" | evergreen (very old Safari needs `-webkit-`) | none |
|
|
272
|
+
| `inert` | removing the host from the a11y tree | Chrome 102 / FF 112 / Safari 15.5 (2023) | degrades to visual + keyboard blocking only |
|
|
273
|
+
| `Element.checkVisibility()` | hiding a marker when its target is hidden | Chrome 105 / FF 125 / Safari 17.4 (2024) | falls back to a 0×0-rect check (detects `display:none` only) |
|
|
274
|
+
|
|
275
|
+
### Module formats
|
|
276
|
+
|
|
277
|
+
- **ESM (default).** `import { initHelpLayer } from 'help-layer'` resolves to the prebuilt, tested
|
|
278
|
+
`dist/help-layer.esm.js` (with `@floating-ui/dom` left external for your bundler/npm to resolve).
|
|
279
|
+
- **No bundler / `<script>` / CDN / strict environments.** Use the self-contained IIFE build, which
|
|
280
|
+
bundles `@floating-ui/dom` and exposes a global `HelpLayer` — see [Use it with just a `<script>`](#use-it-with-just-a-script-no-bundler).
|
|
281
|
+
- **CommonJS (`require`).** No `require` entry is provided: this is a browser-only DOM library, so a Node
|
|
282
|
+
CJS context can't use it meaningfully. In non-ESM toolchains, consume the ESM build via your bundler,
|
|
283
|
+
or load the IIFE build above.
|
|
284
|
+
|
|
253
285
|
## Known limitations
|
|
254
286
|
|
|
255
287
|
- Closed Shadow DOM is unreachable from JS, so it is unsupported (only open shadow roots are pierced).
|
|
256
288
|
- The offset that overlaps the marker onto a corner assumes the default marker size (22px). Changing
|
|
257
289
|
`--help-layer-marker-size` significantly may cause a slight drift.
|
|
290
|
+
- **Target *state* changes are not watched** (only *layout* and *presence* are). While ON, a marker
|
|
291
|
+
follows its target's position/size changes and is added/removed as the target enters/leaves the DOM,
|
|
292
|
+
and it hides/reshows when the target itself is hidden/shown (e.g. `display:none`). However, changes
|
|
293
|
+
to a target's **attributes or content** are *not* detected: adding/removing the `data-help-id`
|
|
294
|
+
attribute on an existing element, rewriting `data-help-title` / `data-help-text`, or toggling state
|
|
295
|
+
like `disabled` won't update the markers. This is intentional — watching every attribute mutation
|
|
296
|
+
across the whole document (`MutationObserver` with `attributes: true, subtree: true`) fires on every
|
|
297
|
+
class/style change and is a performance footgun for a drop-in library. If you change such state,
|
|
298
|
+
rebuild via `update(config)`, toggle the mode OFF→ON, or re-insert the target element into the DOM
|
|
299
|
+
(re-insertion is picked up by the `childList` observation).
|
|
300
|
+
|
|
301
|
+
## Accessibility
|
|
302
|
+
|
|
303
|
+
While the mode is ON, the host app is blocked not only visually and for pointer/keyboard input, but
|
|
304
|
+
also **semantically for assistive technology**: the host is removed from the accessibility tree with
|
|
305
|
+
the [`inert`](https://developer.mozilla.org/docs/Web/HTML/Global_attributes/inert) attribute, so a
|
|
306
|
+
screen reader's virtual cursor (browse mode) can't read or activate background content. Only the help
|
|
307
|
+
markers, the popup, and your toggle stay reachable. The popup is a `role="dialog"` with
|
|
308
|
+
`aria-modal="true"`, and focus moves into it on open and returns to the marker on close.
|
|
309
|
+
|
|
310
|
+
Bounded limitations of this isolation:
|
|
311
|
+
|
|
312
|
+
- `inert` can't be cancelled on a descendant of an inert subtree, so isolation is applied at the
|
|
313
|
+
document-body top level (like the clip-path "hole" that lets the toggle show through). The toggle
|
|
314
|
+
must stay operable, so **the top-level branch containing your toggle is left reachable** — keep the
|
|
315
|
+
toggle at/near the body level to minimize what leaks (none if it's a direct `<body>` child).
|
|
316
|
+
- `inert` is broadly supported in current browsers; on engines without it, the visual/keyboard
|
|
317
|
+
blocking still applies, but AT exclusion degrades gracefully (no error).
|
|
258
318
|
|
|
259
319
|
## Security
|
|
260
320
|
|
|
321
|
+
To report a vulnerability and for the support / security-release policy, see [SECURITY.md](./SECURITY.md). Please use GitHub's private vulnerability reporting rather than a public issue.
|
|
322
|
+
|
|
261
323
|
- By design, `title` / `text` are rendered with `textContent` only; `innerHTML` / `eval` / `new Function` are **never used**.
|
|
262
324
|
- There is **no external communication** (`fetch`, etc.) and **no storage use** (`localStorage` / `cookie`) — it runs fully locally.
|
|
263
325
|
- The only path through which untrusted data is inserted into the DOM as HTML / DOM nodes is the `render` option. Its return value is not sanitized, so
|
package/dist/help-layer.esm.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
|
|
1
|
+
function B(e,{toggleEl:t,isLibraryNode:o}){let r=new Set;function n(a){if(a.nodeType!==1)return;let c=a;o(c)||t&&(c===t||c.contains(t))||c.hasAttribute("inert")||(c.toggleAttribute("inert",!0),r.add(c))}for(let a of[...document.body.children])n(a);let i=new MutationObserver(a=>{for(let c of a)c.addedNodes.forEach(n)});i.observe(document.body,{childList:!0}),e.track(()=>{i.disconnect(),r.forEach(a=>a.removeAttribute("inert")),r.clear()})}var ve=0;function F(){let e=document.createElement("div");return e.className="help-layer-blocking-layer",e}function z(e,t="?"){let o=document.createElement("button");return o.type="button",o.className="help-layer-marker",o.textContent=t,o.setAttribute("aria-label",`Help: ${e}`),o}function H(){let e=`help-layer-popup-title-${ve++}`,t=document.createElement("div");t.className="help-layer-popup",t.setAttribute("role","dialog"),t.setAttribute("aria-modal","true"),t.setAttribute("aria-labelledby",e),t.tabIndex=-1;let o=document.createElement("div");o.className="help-layer-popup__title",o.id=e;let r=document.createElement("div");r.className="help-layer-popup__text";let n=document.createElement("button");return n.type="button",n.className="help-layer-popup__close",n.textContent="\xD7",n.setAttribute("aria-label","Close"),t.append(o,r,n),{root:t,titleEl:o,textEl:r,closeEl:n}}import{autoUpdate as R,computePosition as q,flip as ke,offset as V,shift as we}from"@floating-ui/dom";function K(e,t){let o=e.width||0,r=e.height||0,n=e.left-t.x,i=e.top-t.y;return{x:n,y:i,left:n,top:i,right:n+o,bottom:i+r,width:o,height:r}}function Z(e){return{contextElement:document.body,getBoundingClientRect(){return K(e(),{x:window.scrollX,y:window.scrollY})}}}function G(e,t,o){e.style.left=`${t}px`,e.style.top=`${o}px`}function W(e){if(!(e instanceof Element))return!1;let t=e;for(;t;){if(getComputedStyle(t).position==="fixed")return!0;let o=t.parentElement;if(o)t=o;else{let r=t.getRootNode();t=r instanceof ShadowRoot?r.host:null}}return!1}function Ee(e){if(!(e instanceof Element))return!1;if(typeof e.checkVisibility=="function")return!e.checkVisibility({visibilityProperty:!0,contentVisibilityAuto:!0});let t=e.getBoundingClientRect();return t.width===0&&t.height===0}var O=11;function Le(e){let t=e.endsWith("-start");return{mainAxis:-O,crossAxis:t?O:-O}}function Y(e,t,o,r="top-end",n){let i=W(e)?"fixed":"absolute";i==="fixed"&&t.style.setProperty("position","fixed","important");let a=!1;return R(e,t,()=>{if(Ee(e)){t.style.setProperty("display","none","important"),!a&&n&&n(),a=!0,o&&o();return}a=!1,t.style.removeProperty("display"),q(e,t,{placement:r,strategy:i,middleware:[V(Le(r))]}).then(({x:d,y})=>{G(t,d,y),o&&o()}).catch(()=>{})},{animationFrame:!0})}function U(e,t,o="bottom-start"){let r=W(e)?"fixed":"absolute";t.style.setProperty("position",r,"important");let n=()=>{q(e,t,{placement:o,strategy:r,middleware:[V(8),ke({padding:8}),we({padding:8})]}).then(({x:a,y:c})=>{G(t,a,c)}).catch(()=>{})},i=R(e,t,n,{animationFrame:!0});return{update:n,cleanup:i}}function X(e,t,o){return R(e,t,o)}function Ce(e){let t=e.left,o=e.top,r=e.right,n=e.bottom;return`polygon(
|
|
2
2
|
0px 0px, 100% 0px, 100% 100%, 0px 100%, 0px 0px,
|
|
3
3
|
${t}px ${o}px, ${t}px ${n}px, ${r}px ${n}px, ${r}px ${o}px, ${t}px ${o}px
|
|
4
|
-
)`}function J(e,{toggleEl:t,onBackgroundClick:o,isLibraryElement:r,onEscape:n}){let i=
|
|
4
|
+
)`}function J(e,{toggleEl:t,onBackgroundClick:o,isLibraryElement:r,onEscape:n}){let i=F();if(document.body.appendChild(i),e.track(()=>i.remove()),t){let g=X(t,i,()=>{i.style.clipPath=Ce(t.getBoundingClientRect())});e.track(g)}o&&(i.addEventListener("click",o),e.track(()=>i.removeEventListener("click",o)));let a=document.activeElement;a instanceof HTMLElement&&a!==document.body&&a!==t&&a.blur();let c=f=>{r(f.target)||(f.stopPropagation(),t?t.focus({preventScroll:!0}):f.target instanceof HTMLElement&&f.target.blur())};document.addEventListener("focusin",c,!0),e.track(()=>document.removeEventListener("focusin",c,!0));let d=f=>{r(f.target)||(f.stopPropagation(),f.preventDefault())},y=f=>{if(f.key==="Escape"){f.stopPropagation(),f.preventDefault();return}d(f)},m=f=>{if(f.key==="Escape"){f.stopPropagation(),f.preventDefault(),n&&n();return}d(f)};return document.addEventListener("keydown",m,!0),document.addEventListener("keyup",y,!0),document.addEventListener("keypress",y,!0),e.track(()=>{document.removeEventListener("keydown",m,!0),document.removeEventListener("keyup",y,!0),document.removeEventListener("keypress",y,!0)}),i}function T(e){return typeof e=="object"&&e!==null&&!Array.isArray(e)}function Q(e){return T(e)&&Number.isFinite(e.top)&&Number.isFinite(e.left)}function M(e){if(!T(e))throw new Error("helpConfig must be a plain object");for(let[t,o]of Object.entries(e)){if(!T(o))throw new Error(`helpConfig["${t}"] must be an object`);if(typeof o.title!="string"||o.title==="")throw new Error(`helpConfig["${t}"].title must be a non-empty string`);if(typeof o.text!="string"||o.text==="")throw new Error(`helpConfig["${t}"].text must be a non-empty string`);if(o.position!==void 0&&!Q(o.position))throw new Error(`helpConfig["${t}"].position must be { top: finite number, left: finite number }`)}}function ee(e){return Object.entries(e).map(([t,o])=>Q(o.position)?{key:t,title:o.title,text:o.text,kind:"free",target:null,position:{top:o.position.top,left:o.position.left}}:{key:t,title:o.title,text:o.text,kind:"element",target:null,position:null})}function te(e,t={}){let o=t.minDistance??26,r=t.iterations??6,n=e.map(i=>({x:i.x,y:i.y}));for(let i=0;i<r;i++){let a=!1;for(let c=0;c<n.length;c++)for(let d=c+1;d<n.length;d++){let y=n[c],m=n[d],f=m.x-y.x,g=m.y-y.y,b=Math.hypot(f,g);if(b>=o)continue;b===0&&(f=1,g=0,b=1);let p=(o-b)/2,l=f/b,u=g/b;y.x-=l*p,y.y-=u*p,m.x+=l*p,m.y+=u*p,a=!0}if(!a)break}return n.map((i,a)=>({dx:i.x-e[a].x,dy:i.y-e[a].y}))}var oe="help-layer-target-highlight";function Ae(e){return e.kind==="free"?Z(()=>({top:e.position.top,left:e.position.left,width:0,height:0})):e.target}function ne(e,{onMarkerClick:t,onOverlapResolved:o,onMarkerHidden:r,markerLabel:n="?",markerPlacement:i="top-end"}){let a=new Map,c=null,d=!1;function y(){c=null;let p=[...a.values()].filter(s=>s.el.style.display!=="none");if(p.length<=1){let s=p.length===1?p[0].el:null;s&&s.style.transform&&(s.style.transform="",o&&o());return}p.forEach(s=>{s.el.style.transform=""});let l=p.map(s=>{let h=s.el.getBoundingClientRect();return{x:h.left+h.width/2,y:h.top+h.height/2}}),u=te(l);p.forEach((s,h)=>{let{dx:w,dy:v}=u[h];s.el.style.transform=w||v?`translate(${w}px, ${v}px)`:""}),o&&o()}function m(){c!==null||d||(c=requestAnimationFrame(y))}function f(p){if(a.has(p.id))return;let l=z(p.title,n);document.body.appendChild(l);let u=()=>t(p,l);l.addEventListener("click",u);let s=Y(Ae(p),l,m,i,()=>r&&r(p)),h=p.kind==="element"?p.target:null,w=()=>h&&h.classList.add(oe),v=()=>h&&h.classList.remove(oe);h&&(l.addEventListener("mouseenter",w),l.addEventListener("mouseleave",v),l.addEventListener("focus",w),l.addEventListener("blur",v));let C=!1,A=()=>{C||(C=!0,s(),l.removeEventListener("click",u),h&&(l.removeEventListener("mouseenter",w),l.removeEventListener("mouseleave",v),l.removeEventListener("focus",w),l.removeEventListener("blur",v),v()),l.remove(),a.delete(p.id),m())};a.set(p.id,{record:p,el:l,cleanup:A})}function g(p){let l=a.get(p);l&&l.cleanup()}function b(p){p.forEach(f)}return e.track(()=>{d=!0,c!==null&&(cancelAnimationFrame(c),c=null),[...a.values()].forEach(p=>p.cleanup())}),{mount:f,unmount:g,mountAll:b,has(p){return a.has(p)},findByKey(p){for(let l of a.values())if(l.record.key===p)return l;return null}}}function k(e,t,...o){if(t)try{return t(...o)}catch(r){console.error(`[help-layer] ${e} threw:`,r);return}}var Se=1;function P(e,t,o,r){typeof e.querySelectorAll=="function"&&e.querySelectorAll("*").forEach(n=>{o&&n.matches(t)&&o(n),n.shadowRoot&&(r&&r(n.shadowRoot),P(n.shadowRoot,t,o,r))})}function ie(e,t){let o=[];return P(e,t,r=>o.push(r)),o}function Te(e){let t=[];return P(e,"*",null,o=>t.push(o)),t}function re(e,t){let o=[],r=[];if(e.nodeType!==Se)return{matches:o,shadowRoots:r};let n=e;return typeof n.matches=="function"&&n.matches(t)&&o.push(n),n.shadowRoot&&(r.push(n.shadowRoot),P(n.shadowRoot,t,i=>o.push(i),i=>r.push(i))),P(n,t,i=>o.push(i),i=>r.push(i)),{matches:o,shadowRoots:r}}function ae({root:e=document,selector:t,onAdded:o,onRemoved:r}){let n=new Set,i=d=>{for(let y of d)y.addedNodes.forEach(m=>{let{matches:f,shadowRoots:g}=re(m,t);f.forEach(b=>k("observer onAdded",o,b)),g.forEach(c)}),y.removedNodes.forEach(m=>{re(m,t).matches.forEach(f=>k("observer onRemoved",r,f))})},a=new MutationObserver(i);function c(d){n.has(d)||(n.add(d),a.observe(d,{childList:!0,subtree:!0}))}return c(e),Te(e).forEach(c),{disconnect(){a.disconnect(),n.clear()}}}var $="data-help-title",N="data-help-text";function D(e="data-help-id"){return`[${e}], [${$}]`}function I(e){let t=new Map;for(let o of e)o.kind==="element"&&t.set(o.key,o);return t}function le(e){return e.filter(t=>t.kind==="free").map(t=>({id:t.key,kind:"free",key:t.key,title:t.title,text:t.text,position:t.position}))}function j(e,t,o="data-help-id"){let r=e.getAttribute(o),n=r!=null?t.get(r):void 0,i=n?n.title:e.getAttribute($),a=n?n.text:e.getAttribute(N);return!i||!a?null:{id:e,kind:"element",key:r,title:i,text:a,target:e}}function se(e,t=document,{silent:o=!1,attribute:r="data-help-id"}={}){let n=I(e),i=[];return ie(t,D(r)).forEach(a=>{let c=j(a,n,r);if(!c){if(!o){let d=a.getAttribute(r);console.warn(d!=null?`[help-layer] element with ${r}="${d}" has no matching helpConfig entry or inline ${$}/${N}`:`[help-layer] element needs both ${$} and ${N} (or a ${r} matching helpConfig)`)}return}i.push(c)}),i}function pe(e,{onClose:t,render:o,popupPlacement:r="bottom-start"}={}){let{root:n,titleEl:i,textEl:a,closeEl:c}=H();n.style.setProperty("display","none","important"),document.body.appendChild(n),c.addEventListener("click",()=>l());let d=null,y=null,m=null;function f(){m&&(m.cleanup(),m=null)}function g(u,s){i.textContent=u.title;let h=k("render",o,u);a.textContent="",h?a.appendChild(h):a.textContent=u.text,n.style.setProperty("display","block","important"),d=u.id,y=s,f(),m=U(s,n,r),n.focus({preventScroll:!0})}function b(){m&&m.update()}function p(){let u=d!==null;f(),d=null,y=null,n.style.setProperty("display","none","important"),u&&k("onClose",t)}function l(u){let s=u??y;p(),s&&s.isConnected&&typeof s.focus=="function"&&s.focus({preventScroll:!0})}return e.track(()=>{p(),n.remove()}),{root:n,isOpen(u){return d===u},getOpenId(){return d},open:g,close:l,reposition:b}}function ce(){let e=[];return{track(t){e.push(t)},teardownAll(){for(;e.length>0;){let t=e.pop();try{t()}catch(o){console.error("[help-layer] teardown step threw:",o)}}}}}var Pe="data-help-layer-style",$e=`
|
|
5
5
|
.help-layer-blocking-layer {
|
|
6
6
|
/* Structural properties !important so a host can't accidentally un-fix or restack the layer and
|
|
7
7
|
defeat the blocking guarantee. */
|
|
@@ -156,5 +156,5 @@ var F="help-layer-popup-title";function z(){let e=document.createElement("div");
|
|
|
156
156
|
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.55);
|
|
157
157
|
}
|
|
158
158
|
}
|
|
159
|
-
`;function ue(e){let t=document.createElement("style");return t.setAttribute(
|
|
159
|
+
`;function ue(e){let t=document.createElement("style");return t.setAttribute(Pe,""),e&&t.setAttribute("nonce",e),t.textContent=$e,document.head.appendChild(t),t}function fe(e){e.remove()}function _e(e){if(typeof e=="string"){let t=document.querySelector(e);if(!t)throw new Error(`help-layer: toggle element not found for selector "${e}"`);return t}if(e instanceof HTMLElement)return e;throw new Error("help-layer: toggle must be a CSS selector string or a DOM element")}function de(e){if(!T(e))throw new Error("help-layer: initHelpLayer requires an options object");let{config:t,toggle:o,onEnable:r,onDisable:n,onOpen:i,onClose:a,silent:c=!1,attribute:d="data-help-id",render:y,markerLabel:m="?",markerPlacement:f="top-end",popupPlacement:g="bottom-start",nonce:b}=e,p=t;M(p);let l=o!=null?_e(o):null,u=null,s=null,h=null;function w(){if(u)return;u=ce(),l&&u.track(()=>{l.isConnected&&typeof l.focus=="function"&&l.focus({preventScroll:!0})});let L=ue(b);u.track(()=>fe(L));let E=ee(p),xe=I(E);s=pe(u,{onClose:a,render:y,popupPlacement:g}),h=ne(u,{markerLabel:m,markerPlacement:f,onMarkerClick:(x,S)=>{if(s.isOpen(x.id)){s.close();return}s.open(x,S),k("onOpen",i,x)},onOverlapResolved:()=>s.reposition(),onMarkerHidden:x=>{s.isOpen(x.id)&&s.close(l??void 0)}}),h.mountAll(le(E)),h.mountAll(se(E,document,{silent:c,attribute:d}));let be=ae({selector:D(d),onAdded:x=>{let S=j(x,xe,d);S&&!h.has(S.id)&&h.mount(S)},onRemoved:x=>{s.isOpen(x)&&s.close(l??void 0),h.unmount(x)}});u.track(()=>be.disconnect());let ge=J(u,{toggleEl:l,onBackgroundClick:()=>s.close(),isLibraryElement:x=>!!x&&((l?l.contains(x):!1)||s.root.contains(x)||typeof x.closest=="function"&&!!x.closest(".help-layer-marker")),onEscape:()=>{s.getOpenId()!==null?s.close():A()}});B(u,{toggleEl:l,isLibraryNode:x=>x===ge||x===s.root||!!x.classList&&x.classList.contains("help-layer-marker")})}function v(){u&&(u.teardownAll(),u=null,s=null,h=null)}function C(){u||(w(),k("onEnable",r))}function A(){u&&(v(),k("onDisable",n))}function _(){u?A():C()}function he(L){if(u||C(),!h||!s)return;let E=h.findByKey(L);if(!E){c||console.warn(`[help-layer] open(): no help marker for key "${L}"`);return}s.open(E.record,E.el),k("onOpen",i,E.record)}function me(){s&&s.close()}function ye(L){M(L),p=L,u&&(v(),w())}return l&&l.addEventListener("click",_),{enable:C,disable:A,toggle:_,isActive(){return u!==null},open:he,close:me,update:ye,destroy(){A(),l&&l.removeEventListener("click",_)}}}function bt(e){return de(e)}export{bt as initHelpLayer};
|
|
160
160
|
//# sourceMappingURL=help-layer.esm.js.map
|