haori 0.32.0 → 0.38.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 +12 -1
- package/README.md +12 -1
- package/dist/haori.cjs.js +21 -20
- package/dist/haori.es.js +2716 -1553
- package/dist/haori.iife.js +21 -20
- package/dist/index.d.ts +374 -3
- package/package.json +3 -3
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.38.0
|
|
6
6
|
|
|
7
7
|
---
|
|
8
8
|
|
|
@@ -117,6 +117,12 @@ Haori.mount(document.body, {items: [{name: 'りんご'}, {name: 'みかん'}]});
|
|
|
117
117
|
- `data-poll-*` — タイマーで手続きを繰り返し起動します(定期取得)。別端末や別プロセスでの操作完了を待つ画面に使います。アクション語彙は `data-{event}-*` と共通(`data-poll-fetch`・`data-poll-bind`・`data-poll-bind-arg` など)。設定属性は `data-poll-interval`(取得間隔ミリ秒。既定 5000、下限 100)、`data-poll-timeout`(打ち切りミリ秒。省略時は無制限)、`data-poll-until="{{式}}"`(真になった時点で恒久停止。各リクエスト前とバインド反映後に評価)、`data-poll-error-limit`(連続失敗回数の上限。省略時は継続)、`data-poll-disabled`(真の間は抑止)、`data-poll-state`(`_poll` 状態の注入先。`running`・`paused`・`stopped`・`timedOut`・`stopReason`・`count`・`elapsedMs`)です。初回は即時実行、2回目以降は前回完了時点から計測するためリクエストは多重化せず、`data-if` で非表示の間は一時停止して再表示で再開し、DOM から外れた時点で恒久停止します。バックグラウンドタブではブラウザがタイマーを抑制するため指定間隔は保証されません(タブが表示に戻った時点で即時に取得し直します)。
|
|
118
118
|
- `data-input-*` — テキスト入力1文字ごと(`input` イベント)に手続きを起動します。逐次発火するため `data-input-*` を**明示した要素のみ**が対象(オプトイン)で、`change` 同様に先祖フォームを自動検出して双方向バインディングへ反映します。検索欄の逐次絞り込みなどに使えます(例: `<input name="q" data-input-form>`)。
|
|
119
119
|
- `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
|
+
- `data-validity="{{式}}"` / `data-validity-message="…"` — 入力欄へ宣言するフィールド間検証です。条件を `setCustomValidity()` へ反映してネイティブ検証(`data-{event}-validate`)に相乗りするため、バブル表示・フォーカス移動・`:invalid` の装飾がそのまま使えます。「連絡先いずれか必須」「メールアドレスの一致」のようにネイティブの制約では表現できない条件を宣言できます。
|
|
121
|
+
- `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-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
|
+
- 編集可能な行の中で「取得した候補から選択中の 1 件を引く」構成では、`data-attr-value="{{...}}"` のように**宣言バインドで値が決まる入力は、行の値反映で上書きされません**(評価が解決している間)。評価が未解決のあいだは従来どおり行データの値を表示するため、保存済みレコードから復元した値も失われません。応答は式を書いた要素自身か祖先へバインドする必要があるため、行全体で使う場合は `data-fetch-bind` で**行の内側のラッパ**へ寄せてください(行要素自身を指すと行データへの書き戻しになり、候補一覧が収集値へ入ります)。バインド先の外に置いた宣言は既定値のままになるため、開発モードでは「別のスコープでは供給されているキー」として警告します。
|
|
125
|
+
- 編集可能な行(`data-each` と `data-form-list` の併用)では、行要素をセレクタで指した `data-{event}-copy` / `data-{event}-bind` が、行に対応する**配列要素**へ書き戻されます。行の入力欄の値は配列の要素データが権威なので、これにより他の行に影響せず複数の入力欄へまとめて値を流し込めます(「契約者住所と同じ」の複写や、郵便番号から住所を引いて行へ入れる処理)。`data-form-list` を持つ外側の `<form>` が必要な構成では入れ子の `<form>` を置けないため、行の中に `<form>` を置く書き方の代わりに使います。
|
|
120
126
|
- **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` のようなキー名を並べる属性は評価しません。
|
|
121
127
|
- `data-click-copy-source` — `data-click-copy` のコピー元要素を明示指定します(既定は `data-click-form` のフォーム、無ければイベント発火元の binding)。
|
|
122
128
|
- `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` 併用は避けてください。
|
|
@@ -128,9 +134,13 @@ Haori.mount(document.body, {items: [{name: 'りんご'}, {name: 'みかん'}]});
|
|
|
128
134
|
- `haori:eachupdate` — `data-each` のリスト差分完了時に `data-each` 要素で発火します。発火時点で追加・削除・並べ替えされた全行が DOM に反映され、各行の内容(`{{...}}`)も描画済みのため、描画完了の検知に利用できます(`detail`: `added`・`removed`・`order`・`total`)。
|
|
129
135
|
- `haori:bindcomplete` — `data-*-bind` / `data-*-bind-arg` によるバインドと、対象要素配下の再評価が完了した後に対象要素で発火します(`detail.bindArg`)。
|
|
130
136
|
- `haori:show` / `haori:hide` — `data-if` 要素の表示・非表示時に発火します。
|
|
137
|
+
- `haori:rowadd` / `haori:rowremove` / `haori:rowmove` — `data-each` の差分更新で行ごとに行要素で発火します(`detail`: `key`・`index`・`item` / `key`・`index` / `key`・`from`・`to`)。伝播するためコンテナや `document` でも購読できます。`rowadd` は行内容の描画後、`rowremove` は行が DOM から外れる**前**に発火します。
|
|
138
|
+
- `haori:ready` — 初期化完了時に `document` で発火します(`detail.version`)。購読はライブラリの読み込みより前に登録してください。
|
|
131
139
|
|
|
132
140
|
式中では予約名前空間 `haori` の組み込みヘルパーを利用できます。`haori.date(value, format?, timeZone?)` は ISO 文字列・エポックミリ秒・`Date` を整形し(既定 `yyyy/MM/dd HH:mm`。`timeZone` 省略時はローカル時刻、`'Asia/Tokyo'` 等の IANA タイムゾーン名を渡すとその地域の時刻)、`haori.number(value, decimals?)` は桁区切り付きで数値を整形、`haori.range(start, end?, step?)` は整数配列を生成し(終端排他)、`haori.pages(totalPages, current, {window?, boundary?})` は省略記号付きの番号ページ列を生成します(`current` は 0 始まり。各要素は `{page, label, active, ellipsis}` を持ち `label` は `page + 1`)。月別 UI 向けに、`haori.monthAdd(value, delta)` は `YYYY-MM` 形式の年月へ月数を加算し(`Date` を介さない整数演算でタイムゾーン非依存。不正な入力は空文字)、`haori.monthRange(count, base?)` は基準月から降順に `count + 1` 個の `{targetMonth, label}` を返します(`base` 省略時は現在月)。ページ件数表示向けに、`haori.pageSummary(page, visibleCount?)` は Spring の `Page` 相当(`number`・`size`・`totalElements`/`totalCount`)から `{start, end, total, empty}` を計算します。`haori.findBy(array, key, value)` は配列から `item[key]` が `value` に一致する最初の要素(文字列化比較)を返し、無ければ `null` を返します。`haori.sum(array, key?)` は配列の数値合計を返します(`key` 省略時は要素自体、指定時は `item[key]`、数値化できない値は無視、非配列は `0`)。`haori.distinct(array, key?)` は重複を取り除いた配列を返し(`key` 省略時は要素自体、指定時は `item[key]` で判定。文字列化比較で最初の出現を保持)、`haori.groupBy(array, key)` は `item[key]` ごとに `{key, items}` の配列へ分けます(出現順)。明細を 1 件 1 行へまとめたり、入れ子の `data-each` でグループ表示したりするのに使えます。これにより番号ページネーション(`data-each="haori.pages(totalPages, number, {window: 2})"`)・値の整形(`{{ haori.date(lastUpdatedAt, 'yyyy/MM/dd HH:mm') }}`)・集計行(`{{ haori.number(haori.sum(rows, 'total')) }}`)を宣言的に書けます。同じ関数は `Haori.date` / `Haori.number` / `Haori.range` / `Haori.pages` / `Haori.monthAdd` / `Haori.monthRange` / `Haori.pageSummary` / `Haori.findBy` / `Haori.sum` / `Haori.distinct` / `Haori.groupBy` としても公開されています。`haori` は予約名のため、同名の `data-bind` キーを与えても式中では組み込みが優先されます。
|
|
133
141
|
|
|
142
|
+
`<script src>`(iife)で読み込んだときのグローバル `Haori` は `Haori` クラスそのものです。`Haori.addErrorMessage(...)` のようにクラス API を直接呼び出せ、`Core` や `Env` などは `Haori.Core` / `Haori.Env` として参照します(`Haori.Haori` は自己参照のため、0.37.1 以前の書き方も動作します)。
|
|
143
|
+
|
|
134
144
|
JS からバインドデータを読むには `Haori.Core.getBindingData(element, {resolved?})` を使います。既定では要素自身の生バインドデータ(無ければ `null`)、`resolved: true` で継承を解決済みのスコープを返します(`setBindingData` の対となる読み取り API)。
|
|
135
145
|
|
|
136
146
|
テンプレート式では、プロパティアクセス、動的インデックスを含むブラケットアクセス、optional chaining、三項演算子、配列 `map` / `filter` のアロー関数、spread を伴う呼び出しなどの安全な構文を利用できます。一方で、グローバルオブジェクト、`eval` や `arguments`、`constructor`、`__proto__`、`prototype`、`Reflect`、`Object` などの脱出経路は使用できません。`Object` がブロックされるため、`Object.assign` の代わりにスプレッド構文 `{...a, ...b}` を使ってください。ブロックされた識別子を式で参照すると、コンソールに `blocked identifier(s): …` という警告が出力されます。
|
|
@@ -224,6 +234,7 @@ git push origin --tags
|
|
|
224
234
|
|
|
225
235
|
- `docs/ja/guide.md` — 利用ガイド(サンプル多数)
|
|
226
236
|
- `docs/ja/specs.md` — 技術仕様書(内部設計・API など)
|
|
237
|
+
- `demo/index.html` — 動くデモの一覧(属性ごと・機能ごとの個別デモへ辿れます。`npm run dev:demo` で起動)
|
|
227
238
|
|
|
228
239
|
---
|
|
229
240
|
|
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.38.0
|
|
6
6
|
|
|
7
7
|
---
|
|
8
8
|
|
|
@@ -117,6 +117,12 @@ Event-driven actions:
|
|
|
117
117
|
- `data-poll-*` — run a procedure repeatedly on a timer (interval polling), for screens that wait until another device or process finishes. The action vocabulary is shared with `data-{event}-*` (`data-poll-fetch`, `data-poll-bind`, `data-poll-bind-arg`, …). Configuration is `data-poll-interval` (interval in ms, default 5000, floor 100), `data-poll-timeout` (give up after ms, unlimited when omitted), `data-poll-until="{{expr}}"` (stop permanently once true; evaluated before each request and after each bind), `data-poll-error-limit` (stop after N consecutive failures; keeps going when omitted), `data-poll-disabled` (suppress while truthy) and `data-poll-state` (inject `_poll` state — `running` / `paused` / `stopped` / `timedOut` / `stopReason` / `count` / `elapsedMs`). The first request runs immediately, later intervals are measured from the previous completion (so requests never overlap), polling pauses while hidden by `data-if` and resumes when shown, and stops permanently when the element leaves the DOM. Note that browsers throttle timers in background tabs, so the configured interval is not guaranteed there (an immediate refetch is issued when the tab becomes visible again).
|
|
118
118
|
- `data-input-*` — run a procedure on each keystroke (the `input` event) for text inputs. Because `input` fires incrementally, only elements that explicitly declare a `data-input-*` attribute are handled (opt-in); like `change`, it auto-detects the ancestor form and reflects the value into two-way bindings. Useful for incremental search filtering (e.g. `<input name="q" data-input-form>`).
|
|
119
119
|
- `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
|
+
- `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
|
+
- `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
|
+
- `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
|
+
- 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
|
+
- 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.
|
|
120
126
|
- 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.
|
|
121
127
|
- `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).
|
|
122
128
|
- `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.
|
|
@@ -128,9 +134,13 @@ Lifecycle events:
|
|
|
128
134
|
- `haori:eachupdate` — fired on the `data-each` element after a list diff completes; all added/removed/reordered rows are in the DOM and their content (`{{...}}`) is rendered by the time it fires, so it can be used to detect render completion (`detail`: `added`, `removed`, `order`, `total`).
|
|
129
135
|
- `haori:bindcomplete` — fired on the target element after a `data-*-bind` / `data-*-bind-arg` bind and the subsequent re-evaluation of its subtree complete (`detail.bindArg`).
|
|
130
136
|
- `haori:show` / `haori:hide` — fired when a `data-if` element becomes shown or hidden.
|
|
137
|
+
- `haori:rowadd` / `haori:rowremove` / `haori:rowmove` — fired on each row element during a `data-each` list diff (`detail`: `key`, `index`, `item` / `key`, `index` / `key`, `from`, `to`). They bubble, so the container or `document` can subscribe. `rowadd` fires after the row's content is rendered; `rowremove` fires **before** the row leaves the DOM.
|
|
138
|
+
- `haori:ready` — fired on `document` once initialization completes (`detail.version`). Register the listener before the library script so it is not missed.
|
|
131
139
|
|
|
132
140
|
Built-in helpers are available in expressions under the reserved namespace `haori`: `haori.date(value, format?, timeZone?)` formats an ISO string / epoch ms / `Date` (default `yyyy/MM/dd HH:mm`; local time, or a given IANA time zone such as `'Asia/Tokyo'` when `timeZone` is passed), `haori.number(value, decimals?)` formats numbers with grouping, `haori.range(start, end?, step?)` builds an integer array (end-exclusive), and `haori.pages(totalPages, current, {window?, boundary?})` builds an ellipsis-aware page list (`current` is 0-based; each item exposes `{page, label, active, ellipsis}` with `label` = `page + 1`). For month-based UIs, `haori.monthAdd(value, delta)` adds months to a `YYYY-MM` string (timezone-safe integer math; invalid input returns `''`) and `haori.monthRange(count, base?)` builds a descending list of `count + 1` `{targetMonth, label}` items (`base` defaults to the current month). For pagination summaries, `haori.pageSummary(page, visibleCount?)` turns a Spring-style `Page` (`number`, `size`, `totalElements` / `totalCount`) into `{start, end, total, empty}`. `haori.findBy(array, key, value)` returns the first array element whose `item[key]` matches `value` (stringified comparison), or `null` when none match. `haori.sum(array, key?)` returns the numeric total of an array (the elements themselves when `key` is omitted, or `item[key]`; non-numeric values are ignored; non-arrays yield `0`). `haori.distinct(array, key?)` removes duplicates (by the element itself, or by `item[key]`; stringified comparison, first occurrence kept) and `haori.groupBy(array, key)` groups into `{key, items}` entries (first-seen order) — handy for collapsing detail rows into one row per key or rendering grouped lists with nested `data-each`. These let you build number pagination (`data-each="haori.pages(totalPages, number, {window: 2})"`), format values (`{{ haori.date(lastUpdatedAt, 'yyyy/MM/dd HH:mm') }}`), and total rows (`{{ haori.number(haori.sum(rows, 'total')) }}`) declaratively. The same functions are exposed as `Haori.date` / `Haori.number` / `Haori.range` / `Haori.pages` / `Haori.monthAdd` / `Haori.monthRange` / `Haori.pageSummary` / `Haori.findBy` / `Haori.sum` / `Haori.distinct` / `Haori.groupBy`. `haori` is reserved: a `data-bind` key of the same name does not shadow the built-ins inside expressions.
|
|
133
141
|
|
|
142
|
+
When loaded via `<script src>` (iife), the global `Haori` **is** the `Haori` class: call class APIs directly (`Haori.addErrorMessage(...)`) and reach the other classes as `Haori.Core` / `Haori.Env`. `Haori.Haori` is a self-reference, so code written for 0.37.1 and earlier keeps working.
|
|
143
|
+
|
|
134
144
|
To read binding data from JS, use `Haori.Core.getBindingData(element, {resolved?})` — by default it returns the element's own raw binding data (or `null`), and with `resolved: true` it returns the scope with inheritance resolved (the read counterpart to `setBindingData`).
|
|
135
145
|
|
|
136
146
|
Template expressions support safe 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. 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.
|
|
@@ -224,6 +234,7 @@ For more detailed usage, attribute specs, and internal design, see:
|
|
|
224
234
|
|
|
225
235
|
- `docs/ja/guide.md` — User guide (many examples)
|
|
226
236
|
- `docs/ja/specs.md` — Technical specifications (internal design, API)
|
|
237
|
+
- `demo/index.html` — Catalog of runnable demos, one per attribute/feature (`npm run dev:demo`)
|
|
227
238
|
|
|
228
239
|
---
|
|
229
240
|
|