haori 0.26.2 → 0.27.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/dist/index.d.ts CHANGED
@@ -582,6 +582,40 @@ export declare class Core {
582
582
  * @param newList 新しいリスト
583
583
  */
584
584
  private static updateDiff;
585
+ /**
586
+ * 再利用した `data-each` の行を、新しい並び順の位置へ移動します。
587
+ *
588
+ * `insertTargets` は現在の子並びを表す作業用配列で、移動に合わせて更新します。
589
+ * すでに目的の位置にある場合は何もしません。
590
+ *
591
+ * @param parent `data-each` コンテナのフラグメント
592
+ * @param row 移動対象の行フラグメント
593
+ * @param insertTargets 現在の子並び(この呼び出しで更新される)
594
+ * @param targetIndex 移動先のインデックス
595
+ * @returns 移動完了の Promise
596
+ */
597
+ private static repositionEachRow;
598
+ /**
599
+ * `data-each` の行の入力欄へ、その行の要素データを反映します。
600
+ *
601
+ * `data-each` と `data-form-list` を同一要素へ指定した「編集可能な繰り返し行」では、
602
+ * 行内の入力欄は要素データのキーと `name` で対応します。`Core.setBindingData()` の
603
+ * 逆方向同期(`Form.syncValues`)は `Core.evaluateAll`(= 行生成)より**前**に走る
604
+ * ため、その更新で生成・更新された行には値が入りません。ここで行単位に補います。
605
+ *
606
+ * 呼び出すのは「新規生成した行」と「要素データが変化した再利用行」だけです。
607
+ * 変化していない行へ再適用すると、描画の待ち時間中に利用者が編集した入力欄を
608
+ * 古い値で巻き戻す競合になります(0.26.1 で修正した問題と同種)。行の途中へ要素を
609
+ * 挿入すると以降の行は別の要素データを担当することになるため、変化した再利用行への
610
+ * 適用は必要です(これを省くと挿入位置以降の入力値が前の行のまま残ります)。
611
+ *
612
+ * @param parent `data-each` コンテナのフラグメント
613
+ * @param row 行のフラグメント
614
+ * @param item 行の要素データ
615
+ * @param index 行のインデックス
616
+ * @returns 反映完了の Promise
617
+ */
618
+ private static applyRowFormValues;
585
619
  /**
586
620
  * リスト比較用のキーを生成します。
587
621
  *
@@ -642,7 +676,7 @@ declare class ElementFragment extends Fragment {
642
676
  /** HTML 真偽属性名のセット */
643
677
  private static readonly BOOLEAN_ATTRIBUTES;
644
678
  /** inputイベントを発生させるタイプ */
645
- private readonly INPUT_EVENT_TYPES;
679
+ private static readonly INPUT_EVENT_TYPES;
646
680
  /** 子フラグメントのリスト */
647
681
  private readonly children;
648
682
  /** 属性名に対する属性情報のマップ */
@@ -995,6 +1029,21 @@ declare class ElementFragment extends Fragment {
995
1029
  * @returns 正規化後の値(`type="number"` なら数値または null)
996
1030
  */
997
1031
  private normalizeValueForElement;
1032
+ /**
1033
+ * `value` の宣言バインド(テンプレート式・`data-attr-value`)で DOM プロパティと
1034
+ * 内部値を同期する対象要素かどうかを判定します。
1035
+ *
1036
+ * `value` 属性の反映だけでは `element.value` や内部値(値収集や式評価が参照する値)が
1037
+ * 更新されないため、これらの要素では属性に加えてプロパティも揃えます。
1038
+ * `type="hidden"` は利用者が編集できず送信される値を持つため対象に含めます
1039
+ * (`INPUT_EVENT_TYPES` は `input` イベント発火の可否を決める別目的の一覧なので、
1040
+ * そちらへは追加しません)。checkbox / radio の `value` は送信値であってチェック
1041
+ * 状態ではないため対象外です(状態は `checked` の同期で扱います)。
1042
+ *
1043
+ * @param element 判定対象のエレメント
1044
+ * @returns 同期対象の場合true
1045
+ */
1046
+ static isValuePropertyTarget(element: Element): element is HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement;
998
1047
  /**
999
1048
  * 内部の値をDOMの値と同期します。
1000
1049
  * changeイベント時など、DOM値が変更された後に呼び出されます。
@@ -1026,6 +1075,7 @@ declare class ElementFragment extends Fragment {
1026
1075
  * @param rawName 生の属性名
1027
1076
  * @param targetName 反映先の属性名
1028
1077
  * @param value 生の属性値
1078
+ * @param fromObserver MutationObserver 経由の書き戻しかどうか
1029
1079
  * @returns 属性更新の Promise
1030
1080
  */
1031
1081
  setAliasedAttribute(rawName: string, targetName: string, value: string | null, fromObserver?: boolean): Promise<void>;
@@ -1044,6 +1094,7 @@ declare class ElementFragment extends Fragment {
1044
1094
  * @param targetName 反映先の属性名
1045
1095
  * @param value 生の属性値
1046
1096
  * @param syncValueProperty value 属性更新時に DOM property も同期するかどうか
1097
+ * @param fromObserver MutationObserver 経由の書き戻しかどうか
1047
1098
  * @returns 属性更新の Promise
1048
1099
  */
1049
1100
  private setAttributeInternal;
@@ -1162,6 +1213,23 @@ declare class ElementFragment extends Fragment {
1162
1213
  export declare class Env {
1163
1214
  private static _prefix;
1164
1215
  private static _runtime;
1216
+ private static _strictBind;
1217
+ /**
1218
+ * 厳格バインドモードが有効かどうかを取得します。
1219
+ *
1220
+ * 有効な場合、バインドに無いキーの参照を検出した時点で `error` ログを出力します。
1221
+ * 無効(既定)の場合は正常系として扱い、開発モードで集約警告のみを出します。
1222
+ *
1223
+ * @returns 厳格バインドモードなら true。
1224
+ */
1225
+ static get strictBind(): boolean;
1226
+ /**
1227
+ * 厳格バインドモードを設定します。
1228
+ *
1229
+ * @param enabled 有効にする場合は true。
1230
+ * @return 戻り値はありません。
1231
+ */
1232
+ static setStrictBind(enabled: boolean): void;
1165
1233
  /**
1166
1234
  * 実行モードを取得します。
1167
1235
  *
@@ -1195,6 +1263,14 @@ export declare class Env {
1195
1263
  * 入力要素の値をフォームにバインドし、フォームのバインド値を入力要素に反映します。
1196
1264
  */
1197
1265
  export declare class Form {
1266
+ /**
1267
+ * 初期 `data-bind` からの入力欄復元を適用済みのフォーム要素。
1268
+ *
1269
+ * 復元は「そのフォームを初めてスキャンしたとき」の一度だけ行います。再スキャン
1270
+ * (`data-if` の表示切替など)で繰り返すと、利用者が編集した入力欄を初期値へ
1271
+ * 巻き戻してしまうためです。
1272
+ */
1273
+ private static readonly INITIAL_RESTORED_FORMS;
1198
1274
  /**
1199
1275
  * フォーム内にある入力エレメントの値をオブジェクトとして取得します。
1200
1276
  * data-form-object属性があると、そのエレメント内の値はオブジェクトとして処理されます。
@@ -1226,6 +1302,98 @@ export declare class Form {
1226
1302
  * @returns boolean チェックボックスの場合 true
1227
1303
  */
1228
1304
  private static isBooleanCheckbox;
1305
+ /** ラジオグループのスコープへ割り当てた識別番号 */
1306
+ private static readonly GROUP_SCOPE_IDS;
1307
+ /** ラジオグループのスコープ識別番号の連番 */
1308
+ private static groupScopeSequence;
1309
+ /**
1310
+ * 入力要素の収集キーを解決します。
1311
+ *
1312
+ * `data-form-name` があればそれを収集キーとし、無ければ `name` 属性を使います。
1313
+ * ラジオボタンのように DOM の `name` がグループ化の意味を持つ場合に、収集キーと
1314
+ * DOM の `name` を分けるために使います。
1315
+ *
1316
+ * @param fragment 対象フラグメント
1317
+ * @returns 収集キー。どちらも無い場合は null
1318
+ */
1319
+ static resolveFieldName(fragment: ElementFragment): unknown;
1320
+ /**
1321
+ * `data-form-name` の初期化を行います。
1322
+ *
1323
+ * 収集キーが空になる指定を開発モードで警告し、ラジオボタンにはグループ用の
1324
+ * DOM `name` を生成します。
1325
+ *
1326
+ * HTML のラジオグループは「同じフォームオーナー内の同名要素」で構成されるため、
1327
+ * `data-form-list` の行内で同じ `name` を使うと行をまたいで排他になり、1 行しか
1328
+ * 選択を保持できません。収集キーを `data-form-name` で宣言した場合は、DOM の
1329
+ * `name` を行ごとにユニークな値へ生成してグループを行単位に分けます。
1330
+ *
1331
+ * 作者が `name` を書いている場合は尊重して生成しません(行をまたぐグループを
1332
+ * 意図している場合があるため)。自動生成した `name` は内部マーカーで区別し、
1333
+ * 行の複製で引き継がれたものは作り直します。
1334
+ *
1335
+ * 処理が不要な場合は Promise を返しません。要素初期化の共通経路から呼ばれるため、
1336
+ * 対象外の要素で Promise を挟むと初期化の非同期段数が全要素で増えてしまいます。
1337
+ *
1338
+ * @param fragment 対象フラグメント
1339
+ * @returns 属性設定の Promise。処理が不要な場合は undefined
1340
+ */
1341
+ static prepareFormName(fragment: ElementFragment): Promise<void> | void;
1342
+ /**
1343
+ * ラジオグループのスコープとなるフラグメントを解決します。
1344
+ *
1345
+ * `data-form-list` のコンテナ直下の要素(= 行)が祖先にあればその行を、無ければ
1346
+ * 最近傍のフォーム(`<form>` または `data-form`)をスコープとします。行の外では
1347
+ * 通常の HTML と同じくフォーム単位のグループになります。
1348
+ *
1349
+ * @param fragment 対象フラグメント
1350
+ * @returns スコープとなるフラグメント
1351
+ */
1352
+ private static resolveGroupScope;
1353
+ /**
1354
+ * ラジオグループのスコープへ識別番号を割り当てます。
1355
+ *
1356
+ * @param scope スコープとなるフラグメント
1357
+ * @returns スコープの識別番号
1358
+ */
1359
+ private static resolveGroupScopeId;
1360
+ /**
1361
+ * 値または状態が宣言バインドで決まる入力かどうかを判定します。
1362
+ *
1363
+ * 属性にテンプレート式を書いた場合、または対応する `data-attr-*` を持つ場合は、
1364
+ * その値・状態の権威はバインドの評価結果にあります。値収集側から空で上書きして
1365
+ * はいけません。
1366
+ *
1367
+ * 判定する属性は要素の種類で変わります。checkbox / radio の `value` は送信値で
1368
+ * あってチェック状態ではないため、`value` ではなく `checked` を見ます(`value` で
1369
+ * 判定すると、送信値をテンプレート式で決めているだけのチェックボックスが解除
1370
+ * されなくなり、前の行のチェック状態が残る)。`<select>` は自身の `value` に加えて、
1371
+ * 配下の `<option>` が `selected` を宣言している場合も対象とします。
1372
+ *
1373
+ * @param fragment 対象フラグメント
1374
+ * @returns 宣言バインドで値または状態が決まる場合 true
1375
+ */
1376
+ private static isDeclarativeStateBound;
1377
+ /**
1378
+ * 指定した属性が宣言バインド(テンプレート式または `data-attr-*`)かどうかを
1379
+ * 判定します。
1380
+ *
1381
+ * @param fragment 対象フラグメント
1382
+ * @param name 属性名
1383
+ * @returns 宣言バインドの場合 true
1384
+ */
1385
+ private static hasDeclarativeBinding;
1386
+ /**
1387
+ * `<select>` 配下の `<option>` が選択状態を宣言バインドしているかどうかを
1388
+ * 判定します。
1389
+ *
1390
+ * `name` を持つ select の選択状態を `data-attr-selected` などで宣言している場合、
1391
+ * 選択の権威は option 側の式にあります。
1392
+ *
1393
+ * @param element 対象の select エレメント
1394
+ * @returns いずれかの option が selected を宣言している場合 true
1395
+ */
1396
+ private static hasDeclarativeSelectedOption;
1229
1397
  /**
1230
1398
  * `input[type=file]` かどうかを判定します。
1231
1399
  *
@@ -1262,6 +1430,49 @@ export declare class Form {
1262
1430
  * @returns Promise(DOMの更新が完了したら解決される)
1263
1431
  */
1264
1432
  static syncValues(form: ElementFragment, values: Record<string, unknown>, force?: boolean): Promise<void>;
1433
+ /**
1434
+ * `data-form-list` の 1 行分の入力欄へ、その行の値をイベントなしで反映します。
1435
+ *
1436
+ * `data-each` が新しく生成した行に対して呼び出します。フォーム全体への逆方向同期
1437
+ * (`syncValues()`)は `Core.setBindingData()` の中で `data-each` の行生成より**前**に
1438
+ * 走るため、その更新で生成された行には値が入りません。行単位でここを補います。
1439
+ *
1440
+ * @param row 行のElementFragment
1441
+ * @param values 行に設定する値のオブジェクト
1442
+ * @param index 行のインデックス
1443
+ * @returns 反映完了の Promise
1444
+ */
1445
+ static syncRowValues(row: ElementFragment, values: Record<string, unknown>, index: number): Promise<void>;
1446
+ /**
1447
+ * バインディングデータから、入力欄へ書き戻す対象の値を切り出します。
1448
+ *
1449
+ * `data-form-arg` が指定されている場合はそのキー配下だけを対象とし、キーが
1450
+ * オブジェクトでなければ空オブジェクトを返します(フォーム外のキーを入力欄へ
1451
+ * 書き戻さないため)。指定が無ければバインディングデータ全体が対象です。
1452
+ *
1453
+ * @param form フォームのElementFragment
1454
+ * @param data 対象のバインディングデータ
1455
+ * @returns 入力欄へ書き戻す値
1456
+ */
1457
+ static resolveSyncValues(form: ElementFragment, data: Record<string, unknown>): Record<string, unknown>;
1458
+ /**
1459
+ * 初期 `data-bind` の値を配下の入力欄へ反映します。
1460
+ *
1461
+ * `Core.setBindingData()` 経由の逆方向同期は `data-bind` 属性を**更新した**ときに
1462
+ * だけ走るため、初期スキャンで読み込んだ `data-bind` は入力欄へ反映されません。
1463
+ * その結果、`name` に対応する値を持つ `<select>` やチェックボックスが未選択のまま
1464
+ * 残り、最初の `change` で全項目を収集した際に空値として確定して他項目の値を失う
1465
+ * 問題がありました。本メソッドは初回スキャン時に一度だけ逆方向同期を適用します。
1466
+ *
1467
+ * 対象は `<form>` 要素のうち `data-bind` を持つものだけです(`Core.setBindingData()`
1468
+ * の逆方向同期と同じ範囲)。`data-bind` に含まれないキーの入力欄は
1469
+ * `setPartValues()` の規則により既存値が維持されるため、HTML の `value` 属性で
1470
+ * 与えた初期値は保たれます。
1471
+ *
1472
+ * @param root 走査の起点要素
1473
+ * @returns 反映完了の Promise
1474
+ */
1475
+ static restoreInitialValues(root: HTMLElement): Promise<void>;
1265
1476
  /**
1266
1477
  * 値による上書きをグループ単位で扱うべき入力要素(boolean 型でない
1267
1478
  * チェックボックス、またはラジオボタン)かどうかを判定します。
@@ -1293,6 +1504,8 @@ export declare class Form {
1293
1504
  * @param values フラグメントに設定する値のオブジェクト
1294
1505
  * @param index 配列の場合のインデックス
1295
1506
  * @param force data-form-detach属性があるエレメントにも値を反映するかどうか
1507
+ * @param emitEvents input/change イベントを発火するかどうか
1508
+ * @param clearMissing values に無いキーの入力欄を空にするかどうか
1296
1509
  * @returns Promise(DOMの更新が完了したら解決される)
1297
1510
  */
1298
1511
  private static setPartValues;
@@ -1927,7 +2140,7 @@ declare class TextFragment extends Fragment {
1927
2140
  evaluate(): Promise<void>;
1928
2141
  }
1929
2142
 
1930
- export declare const version = "0.26.2";
2143
+ export declare const version = "0.27.0";
1931
2144
 
1932
2145
  /**
1933
2146
  * すべてのレンダリングタスク(追従投入分を含む)の完了を待ちます。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "haori",
3
- "version": "0.26.2",
3
+ "version": "0.27.0",
4
4
  "description": "A lightweight HTML-first UI engine powered by declarative data bindings and expressions.",
5
5
  "scripts": {
6
6
  "dev": "vite",