haori 0.47.6 → 0.48.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 CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Haori.js は、HTML 属性を中心にして動的な UI を実現する軽量なライブラリです。JavaScript をほとんど書かずに、データバインディング、条件分岐、繰り返し処理、フォームの双方向バインディング、サーバー通信などを HTML 属性で宣言できます。
4
4
 
5
- バージョン: 0.47.6
5
+ バージョン: 0.48.0
6
6
 
7
7
  ---
8
8
 
@@ -101,6 +101,7 @@ Haori.mount(document.body, {items: [{name: 'りんご'}, {name: 'みかん'}]});
101
101
  - `data-attr-xxx` — ブラウザが先に解釈する属性を安全に更新(`src`, `value` など)
102
102
  - `data-value-type` — 入力欄の収集値の型を宣言します(`boolean` / `number` / `string`)。`input` の値は常に文字列のため、真偽値を `type="hidden"` に載せると `"true"` という文字列で送られます。型を宣言すると、収集値・バインドデータ・送信ボディが API の期待する型になります(判定できない値は `null` になるため、未入力を `false` として送りません)。利用者に見せないだけの項目を、真偽値のために「表示しないチェックボックス」で代用する必要がなくなります。`checkbox` / `radio` / `file` と複数選択の `<select>` では無視します(開発モードで警告)。
103
103
  - `data-fetch` — サーバーからデータを取得してバインド
104
+ - `data-fetch-download` / `data-{event}-fetch-download` — 応答本文をファイルとして保存します(CSV エクスポート・PDF ダウンロード)。画面個別の JavaScript が要りません。ファイル名は応答の `Content-Disposition`(RFC 5987 の `filename*` を優先)→ 属性値 → URL 末尾の順で決まります。保存するのは 2xx だけで、それ以外は通常のフェッチと同じエラー振り分けに載るため、**ダウンロードの失敗が画面に出ます**。送信の組み立ては通常のフェッチと同じ(`data-{event}-form` / `-data` / `-fetch-method` / `-fetch-headers`)で、進行状況は `data-{event}-fetch-state` で参照できます。応答はバインドしません(本文は 1 度しか読めないため)。別オリジンでファイル名を使うにはサーバー側で `Access-Control-Expose-Headers: Content-Disposition` が必要です。
104
105
  - `data-import` — 外部 HTML を読み込んで挿入
105
106
  - `data-url-param` — URL のクエリパラメータをバインディングに取り込む
106
107
  - `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 なしで実現できます。
@@ -123,12 +124,13 @@ Haori.mount(document.body, {items: [{name: 'りんご'}, {name: 'みかん'}]});
123
124
  - `data-validity="{{式}}"` / `data-validity-message="…"` — 入力欄へ宣言するフィールド間検証です。条件を `setCustomValidity()` へ反映してネイティブ検証(`data-{event}-validate`)に相乗りするため、バブル表示・フォーカス移動・`:invalid` の装飾がそのまま使えます。「連絡先いずれか必須」「メールアドレスの一致」のようにネイティブの制約では表現できない条件を宣言できます。
124
125
  - `data-{event}-if="{{式}}"`(非イベントは `data-fetch-if`)— 手続きの実行条件です。偽なら fetch・リダイレクト・`data-{event}-run` まで含めて実行しません。どちらも**実行時に同期評価**するため、属性の再描画(`requestAnimationFrame`)を待たず、直前に変更した入力を含めて判定されます(`data-attr-disabled` では「最後の欄を直してそのまま押す」操作で 1 フレーム古い判定になります)。押下のブロックに `disabled` を使わないでください。無効化されたボタンはクリックイベントを発火しないため、「直したのに押せない」方向は救えません。
125
126
  - 応答のバインドより**後**に実行されるアクション(`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` のミラーはバインディングと同期なので、遷移前に必ず完了しています。
126
- - `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` で監視対象から外せます。
127
+ - `data-enhance="名前"` — DOM を走査して機能を付加する外部ライブラリ(Choices.js・郵便番号補完など)を宣言で適用します。`Haori.enhancers.register(名前, {init, refresh, destroy})` で登録すると、初期表示・後から追加された要素・`data-each` の新規行では `init`、`data-each` の描画確定・`data-if` の再表示・フォームのリセット(`data-{event}-reset` とネイティブの `form.reset()` の両方。いずれも値の復元が終わった後)では `refresh`、DOM から外れたときは `destroy` が呼ばれます。適用は要素ごと・名前ごとに一度だけで、走査は宣言した要素の配下に限定されます。未登録の名前は適用を保留し、登録時に遡って適用するため読み込み順に依存しません。登録なしで済ませる簡易形 `data-enhance-new="Global.Ctor"` は、ドット区切りのグローバル参照を対象要素で `new` します(値にコードは書けません)。外部ライブラリの生成 DOM は `data-external` で監視対象から外せます。
127
128
  - 編集可能な行の中で「取得した候補から選択中の 1 件を引く」構成では、`data-attr-value="{{...}}"` のように**宣言バインドで値が決まる入力は、行の値反映で上書きされません**(評価が解決している間)。評価が未解決のあいだは従来どおり行データの値を表示するため、保存済みレコードから復元した値も失われません。応答は式を書いた要素自身か祖先へバインドする必要があるため、行全体で使う場合は `data-fetch-bind` で**行の内側のラッパ**へ寄せてください(行要素自身を指すと行データへの書き戻しになり、候補一覧が収集値へ入ります)。バインド先の外に置いた宣言は既定値のままになるため、開発モードでは「別のスコープでは供給されているキー」として警告します。
128
129
  - 編集可能な行(`data-each` と `data-form-list` の併用)では、行要素をセレクタで指した `data-{event}-copy` / `data-{event}-bind` が、行に対応する**配列要素**へ書き戻されます。行の入力欄の値は配列の要素データが権威なので、これにより他の行に影響せず複数の入力欄へまとめて値を流し込めます(「契約者住所と同じ」の複写や、郵便番号から住所を引いて行へ入れる処理)。`data-form-list` を持つ外側の `<form>` が必要な構成では入れ子の `<form>` を置けないため、行の中に `<form>` を置く書き方の代わりに使います。
129
130
  - **CSS セレクタ**を値に取る属性(`data-{event}-bind`・`-form`・`-copy`・`-copy-source`・`-reset`・`-refetch`・`-click`・`-open`・`-close`・`-adjust`・`-row-*`・`data-fetch-bind`・`data-fetch-state` など)は、照会の前に `{{ ... }}` を評価します。`data-each` の行の中から「その行の要素」を対象にでき(`id="plan-scope-{{i}}"` と `data-change-bind="#plan-scope-{{i}}"` の組み合わせ)、行ごとのバインドや住所複写が属性だけで書けます。不正なセレクタは例外にせずログしてスキップし、単体プレースホルダの未解決参照は「値の指定なし」として扱います(値を省略したときの既定動作になります)。`-bind-arg`・`-copy-params` のようなキー名を並べる属性は評価しません。
130
131
  - `data-click-copy-source` — `data-click-copy` のコピー元要素を明示指定します(既定は `data-click-form` のフォーム、無ければイベント発火元の binding)。
131
132
  - `data-click-no-disabled` / `data-click-defer` — 他ライブラリとの併用補助です。`no-disabled` はクリック手続き実行中に `disabled` 属性を付与せず実行します(Bootstrap collapse など disabled 要素を無視するライブラリ・CSS が動作し続けます。多重実行は内部マーカーで防止)。`defer` はクリック手続きを次フレーム(`requestAnimationFrame`/`setTimeout(0)`)で実行し、他ライブラリの同期 click ハンドラを先に完了させます。遅延後は `preventDefault()` できないため、`<a href>` や `type="submit"` への `defer` 併用は避けてください。
133
+ - `data-{event}-click-await` — `data-{event}-click` が起動した手続きの完了を待ってから次の対象をクリックします。複数の更新を**宣言した順に直列で**送り、**いずれかが失敗した時点で後続を止めます**(HTTP エラー・通信の例外・検証エラー・確認ダイアログのキャンセル)。表示順を 2 件更新する並べ替えが JavaScript なしで書けます。止めた場合は呼び出し元の後続アクション(ダイアログ・トースト・リダイレクト)も実行しません。`data-{event}-if` が偽でスキップされた対象は失敗として扱いません。手続きが同期に起動しない対象(`data-click-defer`、クリックが発火しない `disabled` 要素)は待てず、その旨を記録します。
132
134
  - `data-{event}-prevent`(例: `data-click-prevent`)— そのイベントでブラウザのネイティブなデフォルト動作(`type="submit"` ボタンのフォーム送信、`<a href>` の遷移など)を抑止します。`preventDefault()` はクリックの同期区間で呼ぶため `data-click-defer` と併用しても確実に抑止でき、`stopPropagation()` は呼ばないので他ライブラリのイベント伝播には影響しません。これにより `type="submit"` のまま `data-click-fetch` 等を付けても、ページ再読込なしに動作します。
133
135
  - `data-{event}-run`(例: `data-click-run`・`data-change-run`)— フェッチを伴わず任意の JavaScript をイベント時に実行します。属性値は `new Function` で実 JS として実行され(`-before-run`/`-after-run` と同方式)、`{{...}}` はレンダリング時に展開、`event` が引数で渡されます。本体が `false` を返すと `event.preventDefault()` を呼びます(`onclick="return false"` の慣習)。**セキュリティ**: 展開後の `{{...}}` は実行コードへ結合されるため、信頼できる値(数値 index・自前採番 ID 等)のみを入れてください。API レスポンスやユーザー入力などの信頼できない文字列を入れると任意コード実行(XSS)になり得ます。信頼できない値は `data-bind` 経由で渡し、呼び出す関数の内部で参照してください。
134
136
 
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.47.6
5
+ Version: 0.48.0
6
6
 
7
7
  ---
8
8
 
@@ -101,6 +101,7 @@ Haori.mount(document.body, {items: [{name: 'apple'}, {name: 'orange'}]});
101
101
  - `data-attr-xxx` — safely update browser-interpreted attributes such as `src` and `value`
102
102
  - `data-value-type` — declare the collected type of an input (`boolean` / `number` / `string`). `input.value` is always a string, so a `type="hidden"` field carrying a boolean would otherwise be sent as `"true"`; declaring the type keeps the collected value, the binding data and the request body in the shape the API expects (a value that cannot be judged becomes `null`, so a blank field is never sent as `false`). Values you do not show the user no longer need a hidden checkbox to carry a boolean. Ignored (with a development-mode warning) on `checkbox` / `radio` / `file` and multi-select `<select>`, where the collected value is not a single scalar read from `value`.
103
103
  - `data-fetch` — fetch data from a server and bind the result
104
+ - `data-fetch-download` / `data-{event}-fetch-download` — save the response body as a file (CSV export, PDF download) with a declaration instead of screen-specific JavaScript. The filename comes from the response's `Content-Disposition` (RFC 5987 `filename*` wins), then the attribute value, then the last URL segment. Only 2xx responses are saved; anything else goes through Haori's normal error routing, so a failed download is visible on screen. The request is built exactly like a normal fetch (`data-{event}-form` / `-data` / `-fetch-method` / `-fetch-headers`) and progress is observable through `data-{event}-fetch-state`. The response is not bound (a body can only be read once). Cross-origin responses need `Access-Control-Expose-Headers: Content-Disposition` for the filename.
104
105
  - `data-import` — load external HTML and insert it
105
106
  - `data-url-param` — import URL query parameters into bindings
106
107
  - `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.
@@ -123,12 +124,13 @@ Event-driven actions:
123
124
  - `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.
124
125
  - `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.
125
126
  - 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.
126
- - `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`.
127
+ - `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, when a `data-if` branch is shown again and when a form is reset (both `data-{event}-reset` and a native `form.reset()`, always after the values have been restored), 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`.
127
128
  - 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.
128
129
  - 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.
129
130
  - Attributes that take a **CSS selector** (`data-{event}-bind`, `-form`, `-copy`, `-copy-source`, `-reset`, `-refetch`, `-click`, `-open`, `-close`, `-adjust`, `-row-*`, `data-fetch-bind`, `data-fetch-state`, …) evaluate `{{ ... }}` before querying, so a row inside `data-each` can target *its own* elements (`data-change-bind="#plan-scope-{{i}}"` paired with `id="plan-scope-{{i}}"`). An invalid selector is logged and skipped instead of throwing, and an unresolved single placeholder is treated as "no value given" (falling back to the attribute's default behaviour). Key-list attributes such as `-bind-arg` / `-copy-params` are not evaluated.
130
131
  - `data-click-copy-source` — explicitly set the copy source element for `data-click-copy` (defaults to the form given by `data-click-form`, otherwise the event element's binding).
131
132
  - `data-click-no-disabled` / `data-click-defer` — coexistence helpers for other libraries. `no-disabled` runs the click procedure without adding the `disabled` attribute (so libraries/CSS that ignore disabled elements, e.g. Bootstrap collapse, keep working; double execution is still prevented internally). `defer` runs the click procedure on the next frame (`requestAnimationFrame`/`setTimeout(0)`) so other libraries' synchronous click handlers complete first. Avoid `defer` on `<a href>` / `type="submit"` because the deferred procedure cannot `preventDefault()` the default action.
133
+ - `data-{event}-click-await` — wait for the procedure started by `data-{event}-click` before clicking the next target, so several updates are sent **serially in declaration order** and **the rest is stopped as soon as one fails** (HTTP error, network exception, validation error, or a cancelled confirmation). Row reordering that has to persist two display orders no longer needs JavaScript. When it stops, the calling procedure also skips its remaining actions (dialog, toast, redirect). A target skipped by a false `data-{event}-if` is not a failure. Targets whose procedure does not start synchronously (`data-click-defer`, or a `disabled` element that fires no click) cannot be waited for; that is logged.
132
134
  - `data-{event}-prevent` (e.g. `data-click-prevent`) — suppress the browser's native default action for the event (form submission for a `type="submit"` button, navigation for `<a href>`). `preventDefault()` is called synchronously during the click, so it works even together with `data-click-defer`, and `stopPropagation()` is never called (other libraries' event propagation is unaffected). This lets you keep `type="submit"` and still attach `data-click-fetch` etc. without the page reloading.
133
135
  - `data-{event}-run` (e.g. `data-click-run`, `data-change-run`) — run arbitrary JavaScript on the event without a fetch. The value is executed as real JS via `new Function` (like `-before-run`/`-after-run`), with `{{...}}` expanded at render time and `event` passed as an argument. Returning `false` calls `event.preventDefault()` (the `onclick="return false"` convention). **Security:** the expanded `{{...}}` is concatenated into executable code, so only interpolate trusted values (numeric indexes, IDs you control) — never untrusted strings (API/user input), which would run as code (XSS). Pass untrusted values via `data-bind` and read them inside the called function instead.
134
136