haori 0.41.4 → 0.43.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 +6 -3
- package/README.md +6 -3
- package/dist/haori.cjs.js +20 -20
- package/dist/haori.es.js +2211 -1979
- package/dist/haori.iife.js +23 -23
- package/dist/index.d.ts +116 -3
- package/package.json +1 -1
package/README.ja.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Haori.js は、HTML 属性を中心にして動的な UI を実現する軽量なライブラリです。JavaScript をほとんど書かずに、データバインディング、条件分岐、繰り返し処理、フォームの双方向バインディング、サーバー通信などを HTML 属性で宣言できます。
|
|
4
4
|
|
|
5
|
-
バージョン: 0.
|
|
5
|
+
バージョン: 0.43.0
|
|
6
6
|
|
|
7
7
|
---
|
|
8
8
|
|
|
@@ -104,6 +104,7 @@ Haori.mount(document.body, {items: [{name: 'りんご'}, {name: 'みかん'}]});
|
|
|
104
104
|
- `data-url-param` — URL のクエリパラメータをバインディングに取り込む
|
|
105
105
|
- `data-store` — 宣言したバインディングキーをブラウザストレージへミラーします(1 ストレージキー = 1 JSON レコード)。復元は `data-bind` の直後に処理されるため、`data-if` の条件・`data-each` の配列・入力欄の初期値としてそのまま機能します。対象キーは `data-store-params="a&b"` で選び、`data-store-arg="名前"` でレコード内へネストできます(どちらか一方が必須)。`data-store-type="session|local"` で保存先を選べます(既定 `session`)。保存は対象キーの値が変わったときに自動で行われ(フォームの双方向コミットとフェッチ応答を含む)、バインディングと同期で書き出すため `data-{event}-redirect` の直前でも取りこぼしません。書き込みは宣言したキーだけを置換するので、画面ごとに担当キーを持てます。入力状態を保存する場合は `<form>` 自身に宣言してください。破棄は `data-{event}-store-clear="キー"`(+ `-type`)で行います。これにより複数画面のウィザードを JavaScript なしで実現できます。
|
|
106
106
|
- `data-unauthorized-redirect` / `data-forbidden-redirect` — `<body>`/`<html>` に宣言する認証ガード。Haori の fetch 応答が 401/403 のとき指定 URL(式可)へ遷移します。全 fetch 経路(`data-fetch`・イベント fetch・`data-import`)に適用。ステータス別オプトイン。`*-return-param="クエリ名"` を併用すると、ログイン後復帰用に現在の `pathname+search+hash` を戻り先クエリとして自動付与します(遷移先に同名クエリがあればそちらを優先)。
|
|
107
|
+
- `data-{event}-redirect-replace="URL"` — 履歴を置き換えて遷移します(`location.replace()` 相当)。申込の確定のように一度きりの操作を終えた画面を履歴に残さないため、完了画面から「戻る」で戻って同じ操作をもう一度実行する経路が無くなります。遷移先の評価タイミングは `data-{event}-redirect` と同じで、両方を宣言した場合はこちらを採用して警告します。
|
|
107
108
|
- `data-{event}-redirect-return-param="クエリ名"` — 上記の対称な受け手側。手続きの成功後リダイレクト先を URL クエリから解決し、**安全な同一オリジンのローカルパス**のときのみそこへ遷移します(オープンリダイレクト対策を内蔵)。安全でない/値が無い場合は `data-{event}-redirect` へフォールバック。認証ガードの `*-return-param` と同名クエリで使えば付与 → 消費が対称になり、従来必要だった手書きの検証 JS が不要になります。
|
|
108
109
|
|
|
109
110
|
追加のバインディング補助:
|
|
@@ -119,7 +120,7 @@ Haori.mount(document.body, {items: [{name: 'りんご'}, {name: 'みかん'}]});
|
|
|
119
120
|
- `data-on="イベント名"` + `data-on-*` — `window` / `document` へ dispatch された**任意のカスタムイベント**を契機に手続きを起動します(アクション語彙は `data-{event}-*` と共通)。ネイティブ橋の準備完了通知など、組み込みイベント以外での初期化を宣言的に書けます(例: `<body data-on="appReady" data-on-fetch="/api/init.json" data-on-bind="#app">`)。イベント名は属性値で保持(属性名の小文字化対策)、`window` キャプチャ1本で二重発火なく購読、後挿入要素も追従。組み込みイベント名(click/change/input/load)は警告し購読しません。Haori 購読開始前に発火したイベントは受け取れない点に注意。
|
|
120
121
|
- `data-validity="{{式}}"` / `data-validity-message="…"` — 入力欄へ宣言するフィールド間検証です。条件を `setCustomValidity()` へ反映してネイティブ検証(`data-{event}-validate`)に相乗りするため、バブル表示・フォーカス移動・`:invalid` の装飾がそのまま使えます。「連絡先いずれか必須」「メールアドレスの一致」のようにネイティブの制約では表現できない条件を宣言できます。
|
|
121
122
|
- `data-{event}-if="{{式}}"`(非イベントは `data-fetch-if`)— 手続きの実行条件です。偽なら fetch・リダイレクト・`data-{event}-run` まで含めて実行しません。どちらも**実行時に同期評価**するため、属性の再描画(`requestAnimationFrame`)を待たず、直前に変更した入力を含めて判定されます(`data-attr-disabled` では「最後の欄を直してそのまま押す」操作で 1 フレーム古い判定になります)。押下のブロックに `disabled` を使わないでください。無効化されたボタンはクリックイベントを発火しないため、「直したのに押せない」方向は救えません。
|
|
122
|
-
- 応答のバインドより**後**に実行されるアクション(`data-{event}-redirect`・`-redirect-return-param`・`-dialog`・`-toast`・`-history`・`-scroll`)は、実行する直前に属性を評価します。そのため遷移先やメッセージを応答の値で切り替えられます(`data-click-redirect="{{nextAction === 'pay' ? redirectUrl : '/complete.html'}}"`)。応答は `data-{event}-bind` で自要素または祖先へ反映してください(式のスコープに入る位置が必要です)。式が使っていたキーが手続きの途中で消えた場合(全置換の `data-{event}-bind`)は開始時の評価値を使い、開発モードで警告します(遷移が静かに止まりません)。`data-store` のミラーはバインディングと同期なので、遷移前に必ず完了しています。
|
|
123
|
+
- 応答のバインドより**後**に実行されるアクション(`data-{event}-redirect`・`-redirect-replace`・`-redirect-return-param`・`-dialog`・`-toast`・`-history`・`-scroll`)は、実行する直前に属性を評価します。そのため遷移先やメッセージを応答の値で切り替えられます(`data-click-redirect="{{nextAction === 'pay' ? redirectUrl : '/complete.html'}}"`)。応答は `data-{event}-bind` で自要素または祖先へ反映してください(式のスコープに入る位置が必要です)。式が使っていたキーが手続きの途中で消えた場合(全置換の `data-{event}-bind`)は開始時の評価値を使い、開発モードで警告します(遷移が静かに止まりません)。`data-store` のミラーはバインディングと同期なので、遷移前に必ず完了しています。
|
|
123
124
|
- `data-enhance="名前"` — DOM を走査して機能を付加する外部ライブラリ(Choices.js・郵便番号補完など)を宣言で適用します。`Haori.enhancers.register(名前, {init, refresh, destroy})` で登録すると、初期表示・後から追加された要素・`data-each` の新規行では `init`、`data-each` の描画確定と `data-if` の再表示では `refresh`、DOM から外れたときは `destroy` が呼ばれます。適用は要素ごと・名前ごとに一度だけで、走査は宣言した要素の配下に限定されます。未登録の名前は適用を保留し、登録時に遡って適用するため読み込み順に依存しません。登録なしで済ませる簡易形 `data-enhance-new="Global.Ctor"` は、ドット区切りのグローバル参照を対象要素で `new` します(値にコードは書けません)。外部ライブラリの生成 DOM は `data-external` で監視対象から外せます。
|
|
124
125
|
- 編集可能な行の中で「取得した候補から選択中の 1 件を引く」構成では、`data-attr-value="{{...}}"` のように**宣言バインドで値が決まる入力は、行の値反映で上書きされません**(評価が解決している間)。評価が未解決のあいだは従来どおり行データの値を表示するため、保存済みレコードから復元した値も失われません。応答は式を書いた要素自身か祖先へバインドする必要があるため、行全体で使う場合は `data-fetch-bind` で**行の内側のラッパ**へ寄せてください(行要素自身を指すと行データへの書き戻しになり、候補一覧が収集値へ入ります)。バインド先の外に置いた宣言は既定値のままになるため、開発モードでは「別のスコープでは供給されているキー」として警告します。
|
|
125
126
|
- 編集可能な行(`data-each` と `data-form-list` の併用)では、行要素をセレクタで指した `data-{event}-copy` / `data-{event}-bind` が、行に対応する**配列要素**へ書き戻されます。行の入力欄の値は配列の要素データが権威なので、これにより他の行に影響せず複数の入力欄へまとめて値を流し込めます(「契約者住所と同じ」の複写や、郵便番号から住所を引いて行へ入れる処理)。`data-form-list` を持つ外側の `<form>` が必要な構成では入れ子の `<form>` を置けないため、行の中に `<form>` を置く書き方の代わりに使います。
|
|
@@ -143,7 +144,9 @@ Haori.mount(document.body, {items: [{name: 'りんご'}, {name: 'みかん'}]});
|
|
|
143
144
|
|
|
144
145
|
JS からバインドデータを読むには `Haori.Core.getBindingData(element, {resolved?})` を使います。既定では要素自身の生バインドデータ(無ければ `null`)、`resolved: true` で継承を解決済みのスコープを返します(`setBindingData` の対となる読み取り API)。
|
|
145
146
|
|
|
146
|
-
テンプレート式では、プロパティアクセス、動的インデックスを含むブラケットアクセス、optional chaining、三項演算子、配列 `map` / `filter` のアロー関数、spread
|
|
147
|
+
テンプレート式では、プロパティアクセス、動的インデックスを含むブラケットアクセス、optional chaining、三項演算子、配列 `map` / `filter` のアロー関数、spread を伴う呼び出しなどの構文を利用できます。一方で、グローバルオブジェクト、`eval` や `arguments`、`constructor`、`__proto__`、`prototype`、`Reflect`、`Object` などの脱出経路は遮断されます(計算プロパティ名で組み立てた場合も評価時に遮断します)。`Object` がブロックされるため、`Object.assign` の代わりにスプレッド構文 `{...a, ...b}` を使ってください。ブロックされた識別子を式で参照すると、コンソールに `blocked identifier(s): …` という警告が出力されます。
|
|
148
|
+
|
|
149
|
+
> **セキュリティの前提**: 式のテキストは**開発者が書くコード**です。上記の遮断は事故を難しくする多層防御であり、悪意ある式を防ぐ境界ではありません(式は最終的に `new Function` で評価されます)。**利用者入力や API 応答を式のテキストへ差し込まないでください。** HTML エスケープは式のエスケープではありません(`'` は属性値を読む時点で `'` に戻るため、文字列リテラルの外へ出られます)。信頼できない値は `data-bind` の**値**として渡し、式からはキーで参照してください。詳細は [docs/ja/specs.md](docs/ja/specs.md) の「XSS対策」を参照してください。
|
|
147
150
|
|
|
148
151
|
テスト・デバッグ補助: `waitForRenders()`(`Haori.waitForRenders()` でも可)は、初期化・進行中のフェッチ・キューに積まれた描画タスクがすべて落ち着くまで待機します(E2E テストで描画完了を待つのに便利)。`Haori.Core.dumpScope(element)` は要素に解決されるスコープ(`resolved`)と各キーの由来(`sources`)を返します。開発モードでは falsy な `data-if` がその式と参照スコープを自動でログ出力します。
|
|
149
152
|
|
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Haori.js is a lightweight, HTML-first UI library that enables dynamic user interfaces primarily through HTML attributes. It lets you declare data bindings, conditional rendering, list rendering, form two-way binding, server fetches, and HTML imports without writing much JavaScript.
|
|
4
4
|
|
|
5
|
-
Version: 0.
|
|
5
|
+
Version: 0.43.0
|
|
6
6
|
|
|
7
7
|
---
|
|
8
8
|
|
|
@@ -104,6 +104,7 @@ Haori.mount(document.body, {items: [{name: 'apple'}, {name: 'orange'}]});
|
|
|
104
104
|
- `data-url-param` — import URL query parameters into bindings
|
|
105
105
|
- `data-store` — mirror the declared binding keys to browser storage (one JSON record per storage key), restoring them right after `data-bind` so they work as `data-if` conditions, `data-each` arrays and initial input values. Select the keys with `data-store-params="a&b"` and/or nest them under a record key with `data-store-arg="name"` (one of them is required); `data-store-type="session|local"` picks the storage (default `session`). Saving happens automatically whenever a declared key changes — form two-way commits and fetch responses included — and writes are synchronous with the binding, so a save right before `data-{event}-redirect` is never lost. Writes replace only the declared keys, so screens can each own their part of the record. Declare it on the `<form>` itself to persist input state. Pair with `data-{event}-store-clear="key"` (plus `-type`) to discard the record. Multi-screen wizards can therefore carry state without a single line of JavaScript.
|
|
106
106
|
- `data-unauthorized-redirect` / `data-forbidden-redirect` — auth guard declared on `<body>`/`<html>`. When a Haori fetch responds 401/403, navigate to the given URL (expressions allowed). Applies to all fetch paths (`data-fetch`, event fetches, `data-import`); per-status opt-in. Pair with `*-return-param="name"` to auto-append the current `pathname+search+hash` as a return query for post-login restoration (an existing same-name query on the target URL wins).
|
|
107
|
+
- `data-{event}-redirect-replace="URL"` — navigate by **replacing** the current history entry (`location.replace()`). A screen that finished a one-shot action (submitting an application, for example) is left out of the history, so pressing Back on the completion screen can no longer reach it and run the action again. The destination is evaluated at the same point as `data-{event}-redirect`; when both are declared this one wins and a warning is logged.
|
|
107
108
|
- `data-{event}-redirect-return-param="name"` — the symmetric receiver side. On a successful procedure, resolve the post-redirect destination from the given URL query, navigating there only when it is a safe same-origin local path (open-redirect protection is built in); otherwise fall back to `data-{event}-redirect`. Used with the auth guard's `*-return-param`, append → consume becomes symmetric and the hand-written validation JS is no longer needed.
|
|
108
109
|
|
|
109
110
|
Additional binding helpers:
|
|
@@ -119,7 +120,7 @@ Event-driven actions:
|
|
|
119
120
|
- `data-on="eventName"` + `data-on-*` — run a procedure when an arbitrary **custom event** dispatched on `window` / `document` fires (the action vocabulary is shared with `data-{event}-*`). Lets you declaratively initialize on events other than the built-ins, e.g. a native-bridge ready signal (`<body data-on="appReady" data-on-fetch="/api/init.json" data-on-bind="#app">`). The event name is held in the attribute value (attribute names are lowercased), a single `window` capture subscription receives both `window`- and `document`-dispatched events without double-firing, and elements inserted later are picked up too. Built-in names (click/change/input/load) are warned and not subscribed. Note: events dispatched before Haori subscribes are not received (no replay).
|
|
120
121
|
- `data-validity="{{expr}}"` / `data-validity-message="…"` — declarative cross-field validation on an input. The condition is pushed into `setCustomValidity()`, so it rides on native validation (`data-{event}-validate`): bubble message, focus move and `:invalid` styling all keep working. Conditions that native constraints cannot express ("either phone or e-mail", "the two addresses must match") become declarative.
|
|
121
122
|
- `data-{event}-if="{{expr}}"` (`data-fetch-if` for the non-event form) — an execution condition for the procedure. When false, nothing runs — fetch, redirect and `data-{event}-run` included. Both are evaluated **synchronously at run time**, so they do not wait for attribute re-rendering (`requestAnimationFrame`) and always see the input the user just changed — unlike `data-attr-disabled`, which is one frame stale when you fix the last field and press straight away. Do not use `disabled` to block a click: a disabled button fires no click event at all, so the "fixed it but cannot press" direction cannot be recovered at run time.
|
|
122
|
-
- Actions that run **after** the response is bound (`data-{event}-redirect`, `-redirect-return-param`, `-dialog`, `-toast`, `-history`, `-scroll`) evaluate their attribute right before they run, so the destination or message can be decided by the response (`data-click-redirect="{{nextAction === 'pay' ? redirectUrl : '/complete.html'}}"`). Bind the response to the element itself or an ancestor so the keys are in scope. If a key the expression used disappears mid-procedure (a full-replace `data-{event}-bind`), the value evaluated at procedure start is used instead and a warning is logged in dev mode — navigation is never silently dropped. `data-store` mirroring is synchronous with the binding, so it always completes before the redirect.
|
|
123
|
+
- Actions that run **after** the response is bound (`data-{event}-redirect`, `-redirect-replace`, `-redirect-return-param`, `-dialog`, `-toast`, `-history`, `-scroll`) evaluate their attribute right before they run, so the destination or message can be decided by the response (`data-click-redirect="{{nextAction === 'pay' ? redirectUrl : '/complete.html'}}"`). Bind the response to the element itself or an ancestor so the keys are in scope. If a key the expression used disappears mid-procedure (a full-replace `data-{event}-bind`), the value evaluated at procedure start is used instead and a warning is logged in dev mode — navigation is never silently dropped. `data-store` mirroring is synchronous with the binding, so it always completes before the redirect.
|
|
123
124
|
- `data-enhance="name"` applies a DOM-scanning third-party library (Choices.js, postal-code helpers, …) declaratively. Register it once with `Haori.enhancers.register(name, {init, refresh, destroy})` and Haori calls `init` on the initial scan, on nodes added later and on new `data-each` rows, `refresh` when a `data-each` render settles or a `data-if` branch is shown again, and `destroy` when the element leaves the DOM. Application is once per element per name and the scan is limited to the declaring element's subtree. Unregistered names stay pending and are applied retroactively when registered, so load order does not matter. The registration-free shorthand `data-enhance-new="Global.Ctor"` `new`s a dot-separated global reference with the element as its argument (no code allowed in the value). Keep library-generated DOM out of Haori's observation with `data-external`.
|
|
124
125
|
- Inside an editable row, when you pull "the selected one out of the fetched candidates", inputs whose value is decided by a declarative binding (`data-attr-value="{{...}}"` and friends) are **no longer overwritten by the row data** while the expression resolves. While it is unresolved the row data is applied as before, so a value restored from a saved record is not lost. A response is only visible to the bind target and its descendants, so point `data-fetch-bind` at a **wrapper inside the row** when the whole row needs it (pointing at the row element itself writes through to the row data, putting the candidate list into the collected values). Declarations placed outside the bind target keep their fallback value, and development mode now warns that the key is provided in another scope.
|
|
125
126
|
- In editable rows (`data-each` combined with `data-form-list`), pointing `data-{event}-copy` / `data-{event}-bind` at the **row element** writes through to the matching **array element**. Row input values are owned by that array element, so a single declaration fills several inputs at once without touching the other rows (copying "same as the contract holder's address", or filling a row from a postal-code lookup). Use it where the list-owning `<form>` rules out a nested `<form>` inside the row.
|
|
@@ -145,7 +146,9 @@ To read binding data from JS, use `Haori.Core.getBindingData(element, {resolved?
|
|
|
145
146
|
|
|
146
147
|
To write it, use `Haori.Core.setBindingData(element, data, options?)`. The call is treated as an explicit supply of values: it wins over edits made before the call and loses to edits made after it. If another library rewrote `data-bind` just before the call, that rewrite is ordered first, so **your value is the one that remains**. The optional third argument covers advanced cases (`kind`, `sequence`, `editedPaths`, `clearUserEdits`, `skipFragments`, `reentrant`, `reflectToAttribute`) and is not needed for normal use.
|
|
147
148
|
|
|
148
|
-
Template expressions support
|
|
149
|
+
Template expressions support JavaScript-like syntax such as property access, bracket access with dynamic indexes, optional chaining, ternary expressions, and method chains including array `map`/`filter` with arrow functions and spread calls. Access to global objects, `eval` or `arguments`, and prototype escape paths such as `constructor`, `__proto__`, `prototype`, `Reflect`, or `Object` is blocked — including names assembled at runtime in a computed property, which are checked when the key is evaluated. Because `Object` is blocked, use spread syntax `{...a, ...b}` instead of `Object.assign`; when a blocked identifier is referenced in an expression, a `blocked identifier(s): …` warning is logged to the console.
|
|
150
|
+
|
|
151
|
+
> **Security model:** expression text is **code you write**. The blocks above are defense in depth that make accidents harder, not a boundary against malicious expressions (expressions are ultimately evaluated with `new Function`). **Never interpolate user input or API responses into expression text.** HTML escaping is not expression escaping — `'` turns back into `'` when the attribute value is parsed, which escapes the string literal. Pass untrusted data as `data-bind` **values** and reference it by key from expressions. See "XSS対策" in [docs/ja/specs.md](docs/ja/specs.md) for details.
|
|
149
152
|
|
|
150
153
|
Helpers for tests and debugging: `waitForRenders()` (also `Haori.waitForRenders()`) resolves once initialization, in-flight fetches, and queued render tasks have all settled — useful for E2E tests. `Haori.Core.dumpScope(element)` returns the scope resolved for an element (`resolved`) and where each key comes from (`sources`); in dev mode a falsy `data-if` also logs its expression and referenced scope automatically.
|
|
151
154
|
|