hyperapp-is 0.1.22 → 0.1.24

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.md CHANGED
@@ -153,6 +153,8 @@ npm で非公開の関数 (実験用)は、解説に記載します
153
153
  - [convertJsonToNavigatorItem](#convertjsontonavigatoritem)
154
154
  - [getParentItems](#getparentitems)
155
155
  - [NavigatorFinder](#navigatorfinder)
156
+ - [SearchResult](#searchresult)
157
+ - [NavigatorSearch](#navigatorsearch)
156
158
 
157
159
  **animation / step.ts**
158
160
  - [effect_throwMessageStart](#effect_throwmessagestart)
@@ -221,6 +223,7 @@ src
221
223
  │ └ navigator.ts
222
224
  │ Keys_NavigatorItem, NavigatorItem, JsonEntry, NavigatorColumn
223
225
  │ convertJsonToNavigatorItem, getParentItems, NavigatorFinder
226
+ │ SearchResult, NavigatorSearch
224
227
 
225
228
  ├ animation
226
229
  │ ├ step.ts
@@ -619,10 +622,7 @@ export const NavigatorFinder = function <S> (
619
622
  state : S
620
623
  localState: Record<string, any>
621
624
  }) => VNode<S>[]
622
- afterRender ?: (props: {
623
- state : S
624
- localState: Recrod<string, any>
625
- }, vnode: VNode<S>) => VNode<S>
625
+ afterRender ?: (state: S, localState: Recrod<string, any>, vnode: VNode<S>) => VNode<S>
626
626
  [key: string]: any
627
627
  }
628
628
  ): VNode<S>
@@ -635,6 +635,13 @@ export const NavigatorFinder = function <S> (
635
635
  - plugIn : プラグインの挿入
636
636
  - afterRender : レンダーフック
637
637
 
638
+ localState
639
+ - searchText: "" as string // 検索テキスト
640
+ - selected : [] as string[] // 選択されているボタン名
641
+ - sortType : undefined // ソート用比較関数: (a: NavigatorItem, b: NavigatorItem) => number
642
+ - reverse : false as boolean // ソートを逆順にするか
643
+ - sortKey : undefined as undefined | string // 使用されているソート名 (column.name)
644
+
638
645
  vnode
639
646
  ```html
640
647
  <div id={id}>
@@ -676,9 +683,51 @@ vnode
676
683
  </div>
677
684
  ```
678
685
 
679
- **CSS設計**
680
- - plugInを追加する場合は `grid` で整える前提
681
- - `flex` で整える場合は `afterRender` を併用し VNode を調整する
686
+ ---
687
+
688
+ ### SearchResult
689
+ `NavigatorSearch` で検索結果となる構造体
690
+
691
+ ```ts
692
+ export interface SearchResult {
693
+ item : NavigatorItem
694
+ depth: number
695
+ }
696
+ ```
697
+
698
+ ---
699
+
700
+ ### NavigatorSearch
701
+ `NavigatorFinder` プラグイン
702
+
703
+ ```ts
704
+ export const NavigatorSearch = function <S> (
705
+ props: {
706
+ state : S
707
+ id : string
708
+ currentKeys : Keys_NavigatorItem
709
+ searchResult : (item: NavigatorItem, depth: number) => VNode<S> | VNode<S>[]
710
+ hitTest : (item: NavigatorItem) => boolean
711
+ maxItemsCount : number
712
+ afterRender ?: (state: S, localState: Record<string, any>, vnode: VNode<S>) => VNode<S>
713
+ [key: string]: any
714
+ }
715
+ ): VNode<S>
716
+ ```
717
+
718
+ - state : ステート
719
+ - id : ユニークID (DOM ID)
720
+ - searchResult : カードとして表示する VNode
721
+ - hitTest : 抽出条件
722
+ - maxItemsCount: 最初に表示するカードの最大数
723
+ - afterRender : レンダーフック
724
+
725
+ localState
726
+ - maxItemsCount: props.maxItemsCount as number // カードの最大表示数
727
+ - sortName : undefined as undefined | string // ソート名
728
+ - sortFn : undefined as undefined | ((a: SearchResult, b: SearchResult) => number) // 比較関数
729
+ - isDirectory : true // ディレクトリを表示
730
+ - isFile : true // ファイルを表示
682
731
 
683
732
  ---
684
733
 
@@ -35,15 +35,12 @@ export declare const getParentItems: (item: NavigatorItem | undefined) => Naviga
35
35
  * - 必須項目
36
36
  * state, id, currentKeys
37
37
  *
38
+ * - 拡張項目
39
+ * columns, plugIn, afterRender
40
+ *
38
41
  * - columns
39
42
  * カラムの表示設定
40
43
  *
41
- * - maxItemsCount
42
- * 最大表示させるアイテム数
43
- *
44
- * - itemClick
45
- * 非ノードをクリックした際のアクション
46
- *
47
44
  * - plugIn
48
45
  * プラグインの追加
49
46
  *
@@ -55,13 +52,51 @@ export declare const NavigatorFinder: <S>(props: {
55
52
  id: string;
56
53
  currentKeys: Keys_NavigatorItem;
57
54
  columns?: (directory: NavigatorItem | undefined) => NavigatorColumn[];
58
- plugIn?: (props: {
59
- state: S;
60
- localState: Record<string, any>;
61
- }) => VNode<S>[];
62
- afterRender?: (props: {
63
- state: S;
64
- localState: Record<string, any>;
65
- }, vnode: VNode<S>) => VNode<S>;
55
+ plugIn?: (state: S, localState: Record<string, any>) => VNode<S>[];
56
+ afterRender?: (state: S, localState: Record<string, any>, vnode: VNode<S>) => VNode<S>;
57
+ [key: string]: any;
58
+ }) => VNode<S>;
59
+ /**
60
+ * 検索結果
61
+ *
62
+ * - item
63
+ * ヒットしたアイテム
64
+ *
65
+ * - depth
66
+ * current からの深度
67
+ */
68
+ export interface SearchResult {
69
+ item: NavigatorItem;
70
+ depth: number;
71
+ }
72
+ /**
73
+ * ファイルサーチ - NavigatorFinder プラグイン
74
+ *
75
+ * - 必須項目
76
+ * state, id, currentKeys, searchResult, hitTest, maxItemsCount
77
+ *
78
+ * - searchResult
79
+ * カードとして表示するVNode
80
+ *
81
+ * - hitTest
82
+ * 抽出条件
83
+ *
84
+ * - maxItemsCount
85
+ * 最初に表示させるカードの最大数
86
+ *
87
+ * - 拡張項目
88
+ * afterRender
89
+ *
90
+ * - afterRender
91
+ * レンダーフック
92
+ */
93
+ export declare const NavigatorSearch: <S>(props: {
94
+ state: S;
95
+ id: string;
96
+ currentKeys: Keys_NavigatorItem;
97
+ searchResult: (item: NavigatorItem, depth: number) => VNode<S> | VNode<S>[];
98
+ hitTest: (item: NavigatorItem) => boolean;
99
+ maxItemsCount: number;
100
+ afterRender?: (state: S, localState: Record<string, any>, vnode: VNode<S>) => VNode<S>;
66
101
  [key: string]: any;
67
102
  }) => VNode<S>;
@@ -1,5 +1,6 @@
1
1
  // hyperapp-is / core / navigator.ts
2
- import { getValue, setValue, getLocalState, setLocalState, createLocalKey } from "./state";
2
+ import { getValue, setValue, getLocalState, setLocalState, createLocalKey, } from "./state";
3
+ import { getScrollMargin } from "../dom/utils";
3
4
  import { el, deleteKeys, SelectButton } from "./component";
4
5
  // ---------- ---------- ---------- ---------- ----------
5
6
  // convertJsonToNavigatorItem
@@ -61,7 +62,6 @@ export const getParentItems = (item) => {
61
62
  // vnodes
62
63
  // ---------- ---------- ---------- ---------- ----------
63
64
  const div = el("div");
64
- const section = el("section");
65
65
  const table = el("table");
66
66
  const thead = el("thead");
67
67
  const tbody = el("tbody");
@@ -74,6 +74,9 @@ const li = el("li");
74
74
  const button = el("button");
75
75
  const input = el("input");
76
76
  const span = el("span");
77
+ const svg = el("svg");
78
+ const rect = el("rect");
79
+ const path = el("path");
77
80
  // ---------- ---------- ---------- ---------- ----------
78
81
  // NavigatorFinder Component
79
82
  // ---------- ---------- ---------- ---------- ----------
@@ -83,15 +86,12 @@ const span = el("span");
83
86
  * - 必須項目
84
87
  * state, id, currentKeys
85
88
  *
89
+ * - 拡張項目
90
+ * columns, plugIn, afterRender
91
+ *
86
92
  * - columns
87
93
  * カラムの表示設定
88
94
  *
89
- * - maxItemsCount
90
- * 最大表示させるアイテム数
91
- *
92
- * - itemClick
93
- * 非ノードをクリックした際のアクション
94
- *
95
95
  * - plugIn
96
96
  * プラグインの追加
97
97
  *
@@ -101,24 +101,16 @@ const span = el("span");
101
101
  export const NavigatorFinder = function (props) {
102
102
  var _a, _b;
103
103
  const { state, id, currentKeys, plugIn, afterRender } = props;
104
+ // current
104
105
  const current = getValue(state, currentKeys, undefined);
105
106
  // localState
106
107
  const localState = getLocalState(state, id, {
107
108
  searchText: "", // 検索テキスト
108
109
  selected: [], // 選択されているボタン名
109
- sortType: undefined, // ソート用比較関数
110
+ sortType: undefined, // ソート用比較関数: (a: NavigatorItem, b: NavigatorItem) => number
110
111
  reverse: false, // ソートを逆順にするか
111
112
  sortKey: undefined // 使用されているソート名 (column.name)
112
113
  });
113
- /*
114
- const { searchText, selected, sortType, reverse, sortKey } = getLocalState(state, id, {
115
- searchText: "", // 検索テキスト
116
- selected : [], // 選択されているボタン名
117
- sortType : undefined, // ソート用比較関数
118
- reverse : false, // ソートを逆順にするか
119
- sortKey : undefined // 使用されているソート名 (column.name)
120
- })
121
- */
122
114
  // selected filter
123
115
  const isFilter = localState.selected.includes(`${createLocalKey(id)}_filter`);
124
116
  // ---------- ---------- ----------
@@ -319,10 +311,261 @@ export const NavigatorFinder = function (props) {
319
311
  div({ class: "statusBar" }, message),
320
312
  // plugIn
321
313
  plugIn
322
- ? plugIn({ state, localState })
314
+ ? plugIn(state, localState)
323
315
  : []);
324
316
  // ---------- ---------- ----------
325
317
  // afterRender
326
318
  // ---------- ---------- ----------
327
- return afterRender ? afterRender({ state, localState }, vnode) : vnode;
319
+ return afterRender ? afterRender(state, localState, vnode) : vnode;
320
+ };
321
+ // ---------- ---------- ---------- ---------- ----------
322
+ // svg icon
323
+ // ---------- ---------- ---------- ---------- ----------
324
+ // icon_depth
325
+ const icon_depth = svg({
326
+ width: 24,
327
+ height: 24,
328
+ viewBox: "0 0 24 24",
329
+ fill: "none",
330
+ stroke: "currentColor",
331
+ strokeWidth: 2,
332
+ strokeLinecap: "round",
333
+ strokeLinejoin: "round"
334
+ }, rect({
335
+ x: 9,
336
+ y: 3,
337
+ width: 6,
338
+ height: 4,
339
+ rx: 1
340
+ }), rect({
341
+ x: 3,
342
+ y: 15,
343
+ width: 6,
344
+ height: 4,
345
+ rx: 1
346
+ }), rect({
347
+ x: 15,
348
+ y: 15,
349
+ width: 6,
350
+ height: 4,
351
+ rx: 1
352
+ }), path({
353
+ d: "M12 7v4"
354
+ }), path({
355
+ d: "M6 15v-4h12v4"
356
+ }));
357
+ // icon_name
358
+ const icon_name = svg({
359
+ width: 24,
360
+ height: 24,
361
+ viewBox: "0 0 24 24",
362
+ fill: "none",
363
+ stroke: "currentColor",
364
+ strokeWidth: 2,
365
+ strokeLinecap: "round",
366
+ strokeLinejoin: "round"
367
+ }, path({ d: "M4 18l2-8 2 8M5 14h2" }), path({ d: "M10 10h6l-6 8h6" }), path({ d: "M20 6v12" }), path({ d: "M17 15l3 3 3-3" }));
368
+ // icon_directory
369
+ const icon_directory = svg({
370
+ width: 24,
371
+ height: 24,
372
+ viewBox: "0 0 24 24",
373
+ fill: "none",
374
+ stroke: "currentColor",
375
+ strokeWidth: 2,
376
+ strokeLinecap: "round",
377
+ strokeLinejoin: "round"
378
+ }, path({ d: "M3 7h6l2 2h10v8a2 2 0 0 1-2 2H3z" }));
379
+ // icon_file
380
+ const icon_file = svg({
381
+ width: 24,
382
+ height: 24,
383
+ viewBox: "0 0 24 24",
384
+ fill: "none",
385
+ stroke: "currentColor",
386
+ strokeWidth: 2,
387
+ strokeLinecap: "round",
388
+ strokeLinejoin: "round"
389
+ }, path({ d: "M6 2h9l5 5v15a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2z" }), path({ d: "M14 2v6h6" }));
390
+ // ---------- ---------- ---------- ---------- ----------
391
+ // NavigatorSearch Component (NavigatorFinder plugIn)
392
+ // ---------- ---------- ---------- ---------- ----------
393
+ /**
394
+ * ファイルサーチ - NavigatorFinder プラグイン
395
+ *
396
+ * - 必須項目
397
+ * state, id, currentKeys, searchResult, hitTest, maxItemsCount
398
+ *
399
+ * - searchResult
400
+ * カードとして表示するVNode
401
+ *
402
+ * - hitTest
403
+ * 抽出条件
404
+ *
405
+ * - maxItemsCount
406
+ * 最初に表示させるカードの最大数
407
+ *
408
+ * - 拡張項目
409
+ * afterRender
410
+ *
411
+ * - afterRender
412
+ * レンダーフック
413
+ */
414
+ export const NavigatorSearch = function (props) {
415
+ const { state, id, currentKeys, searchResult, hitTest, afterRender } = props;
416
+ // localKey
417
+ const localKey = createLocalKey(id);
418
+ // localState
419
+ const localState = getLocalState(state, id, {
420
+ maxItemsCount: props.maxItemsCount, // カードの最大表示数
421
+ sortName: undefined, // ソート名
422
+ sortFn: undefined, // 比較関数
423
+ isDirectory: true, // ディレクトリを表示
424
+ isFile: true // ファイルを表示
425
+ });
426
+ // current
427
+ const current = getValue(state, currentKeys, undefined);
428
+ // ---------- ---------- ----------
429
+ // searchItems
430
+ // ---------- ---------- ----------
431
+ /**
432
+ * アイテムの検索
433
+ * 検索アイテムのフラット化をしていないので、速度によっては拡張を検討
434
+ */
435
+ const searchItems = (item, depth) => {
436
+ if (!item)
437
+ return [];
438
+ const result = [];
439
+ if (hitTest(item))
440
+ result.push({ item, depth });
441
+ if (item.children && item.children.length !== 0) {
442
+ item.children.forEach(child => {
443
+ const r = searchItems(child, depth + 1);
444
+ if (r.length !== 0)
445
+ result.push(...r);
446
+ });
447
+ }
448
+ return result;
449
+ }; // end searchItems
450
+ // get items
451
+ const items = current
452
+ ? searchItems(current, 0)
453
+ : [];
454
+ // sort
455
+ if (localState.sortFn !== undefined)
456
+ items.sort(localState.sortFn);
457
+ // drawItems (filter, maxItemsCount の適用)
458
+ const drawItems = items.filter(item => {
459
+ return item.item.children
460
+ ? localState.isDirectory
461
+ : localState.isFile;
462
+ }).slice(0, localState.maxItemsCount);
463
+ // get parentItems
464
+ const parentItems = current
465
+ ? getParentItems(current).concat(current)
466
+ : [];
467
+ // message
468
+ const message = `hit ${items.length} items`;
469
+ // ---------- ---------- ----------
470
+ // action_itemsScroll
471
+ // ---------- ---------- ----------
472
+ /**
473
+ * items のスクロール状況で、maxItemsCount を追加
474
+ */
475
+ const action_itemsScroll = (state, e) => {
476
+ const margin = getScrollMargin(e);
477
+ return setLocalState(state, id, {
478
+ maxItemsCount: margin.bottom < 10
479
+ ? localState.maxItemsCount + 10 < items.length
480
+ ? localState.maxItemsCount + 10
481
+ : Math.max(10, items.length)
482
+ : localState.maxItemsCount
483
+ });
484
+ };
485
+ // ---------- ---------- ----------
486
+ // action_setSort
487
+ // ---------- ---------- ----------
488
+ /**
489
+ * 仕様するソート名をセット
490
+ * sortFn をステートに持つように変更する場合、ここも修正
491
+ */
492
+ const action_setSort = (state, newSortName) => {
493
+ const reverse = localState.sortName === newSortName;
494
+ const obj = {
495
+ "depth": (a, b) => a.depth - b.depth,
496
+ "name": (a, b) => {
497
+ if (a.item.name === b.item.name)
498
+ return 0;
499
+ return a.item.name < b.item.name ? -1 : 1;
500
+ }
501
+ };
502
+ const fn = obj[newSortName] !== undefined
503
+ ? reverse
504
+ ? (a, b) => obj[newSortName](b, a)
505
+ : (a, b) => obj[newSortName](a, b)
506
+ : (a, b) => {
507
+ return a.depth === b.depth
508
+ ? obj["name"](a, b)
509
+ : obj["depth"](a, b);
510
+ };
511
+ return setLocalState(state, id, {
512
+ sortName: reverse ? `r_${newSortName}` : newSortName,
513
+ sortFn: fn
514
+ });
515
+ };
516
+ // ---------- ---------- ----------
517
+ // vnode
518
+ // ---------- ---------- ----------
519
+ const vnode = div({
520
+ ...deleteKeys(props, "state", "currentKeys", "searchResult", "hitTest", "maxItemsCount", "afterRender")
521
+ },
522
+ // toolBar
523
+ div({
524
+ class: "toolBar"
525
+ },
526
+ // sort
527
+ button({
528
+ type: "button",
529
+ onclick: [action_setSort, "depth"]
530
+ }, icon_depth), button({
531
+ type: "button",
532
+ onclick: [action_setSort, "name"]
533
+ }, icon_name),
534
+ // filter
535
+ button({
536
+ type: "button",
537
+ class: localState.isDirectory ? "" : "ignore",
538
+ onclick: (state) => setLocalState(state, id, {
539
+ isDirectory: !localState.isDirectory
540
+ })
541
+ }, icon_directory), button({
542
+ type: "button",
543
+ class: localState.isFile ? "" : "ignore",
544
+ onclick: (state) => setLocalState(state, id, {
545
+ isFile: !localState.isFile
546
+ })
547
+ }, icon_file)),
548
+ // parentItems
549
+ div({
550
+ class: "parentItems"
551
+ }, ol({}, parentItems.map(item => li({
552
+ onclick: (state) => setValue(state, currentKeys, item)
553
+ }, item.name)))),
554
+ // items
555
+ div({
556
+ class: "items",
557
+ onscroll: action_itemsScroll
558
+ }, ul({}, drawItems.map(item => li({
559
+ class: "item",
560
+ key: item.item.path,
561
+ title: item.item.path
562
+ }, searchResult(item.item, item.depth))))),
563
+ // statusBar
564
+ div({
565
+ class: "statusBar"
566
+ }, message));
567
+ // ---------- ---------- ----------
568
+ // afterRender
569
+ // ---------- ---------- ----------
570
+ return afterRender ? afterRender(state, localState, vnode) : vnode;
328
571
  };
@@ -1,8 +1,8 @@
1
1
  export type { Keys, Keys_String, Keys_ArrayString, Keys_Number, Keys_ArrayNumber, Keys_ArrayRAFTask, Keys_NavigatorItem, } from "./core/state";
2
- export type { NavigatorItem, JsonEntry, NavigatorColumn } from "./core/navigator";
2
+ export type { NavigatorItem, JsonEntry, NavigatorColumn, SearchResult } from "./core/navigator";
3
3
  export { getValue, setValue, getLocalState, setLocalState, createLocalKey } from "./core/state";
4
4
  export { el, concatAction, getClassList, deleteKeys, Route, SelectButton, OptionButton } from "./core/component";
5
- export { convertJsonToNavigatorItem, getParentItems, NavigatorFinder } from "./core/navigator";
5
+ export { convertJsonToNavigatorItem, getParentItems, NavigatorFinder, NavigatorSearch } from "./core/navigator";
6
6
  export type { InternalEffect, RAFEvent } from "./animation/raf";
7
7
  export type { CSSProperty } from "./animation/properties";
8
8
  export { RAFTask, subscription_RAFManager } from "./animation/raf";
@@ -1,7 +1,7 @@
1
1
  // hyperapp-is / index.ts
2
2
  export { getValue, setValue, getLocalState, setLocalState, createLocalKey } from "./core/state";
3
3
  export { el, concatAction, getClassList, deleteKeys, Route, SelectButton, OptionButton } from "./core/component";
4
- export { convertJsonToNavigatorItem, getParentItems, NavigatorFinder } from "./core/navigator";
4
+ export { convertJsonToNavigatorItem, getParentItems, NavigatorFinder, NavigatorSearch } from "./core/navigator";
5
5
  export { RAFTask, subscription_RAFManager } from "./animation/raf";
6
6
  export { progress_easing } from "./animation/easing";
7
7
  export { createUnits, createRAFProperties, effect_RAFProperties } from "./animation/properties";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hyperapp-is",
3
- "version": "0.1.22",
3
+ "version": "0.1.24",
4
4
  "description": "UI foundation library for Hyperapp by is4416",
5
5
  "license": "MIT",
6
6
  "type": "module",