hyperapp-is 0.1.22 → 0.1.23

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
@@ -635,6 +638,13 @@ export const NavigatorFinder = function <S> (
635
638
  - plugIn : プラグインの挿入
636
639
  - afterRender : レンダーフック
637
640
 
641
+ localState
642
+ - searchText: "" as string // 検索テキスト
643
+ - selected : [] as string[] // 選択されているボタン名
644
+ - sortType : undefined // ソート用比較関数: (a: NavigatorItem, b: NavigatorItem) => number
645
+ - reverse : false as boolean // ソートを逆順にするか
646
+ - sortKey : undefined as undefined | string // 使用されているソート名 (column.name)
647
+
638
648
  vnode
639
649
  ```html
640
650
  <div id={id}>
@@ -676,9 +686,54 @@ vnode
676
686
  </div>
677
687
  ```
678
688
 
679
- **CSS設計**
680
- - plugInを追加する場合は `grid` で整える前提
681
- - `flex` で整える場合は `afterRender` を併用し VNode を調整する
689
+ ---
690
+
691
+ ### SearchResult
692
+ `NavigatorSearch` で検索結果となる構造体
693
+
694
+ ```ts
695
+ export interface SearchResult {
696
+ item : NavigatorItem
697
+ depth: number
698
+ }
699
+ ```
700
+
701
+ ---
702
+
703
+ ### NavigatorSearch
704
+ `NavigatorFinder` プラグイン
705
+
706
+ ```ts
707
+ export const NavigatorSearch = function <S> (
708
+ props: {
709
+ state : S
710
+ id : string
711
+ currentKeys : Keys_NavigatorItem
712
+ searchResult : (item: NavigatorItem, depth: number) => VNode<S> | VNode<S>[]
713
+ hitTest : (item: NavigatorItem) => boolean
714
+ maxItemsCount : number
715
+ afterRender ?: (props: {
716
+ state : S
717
+ localState: Record<string, any>
718
+ }, vnode: VNode<S>) => VNode<S>
719
+ [key: string]: any
720
+ }
721
+ ): VNode<S>
722
+ ```
723
+
724
+ - state : ステート
725
+ - id : ユニークID (DOM ID)
726
+ - searchResult : カードとして表示する VNode
727
+ - hitTest : 抽出条件
728
+ - maxItemsCount: 最初に表示するカードの最大数
729
+ - afterRender : レンダーフック
730
+
731
+ localState
732
+ - maxItemsCount: props.maxItemsCount as number // カードの最大表示数
733
+ - sortName : undefined as undefined | string // ソート名
734
+ - sortFn : undefined as undefined | ((a: SearchResult, b: SearchResult) => number) // 比較関数
735
+ - isDirectory : true // ディレクトリを表示
736
+ - isFile : true // ファイルを表示
682
737
 
683
738
  ---
684
739
 
@@ -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,10 +52,54 @@ 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: {
55
+ plugIn?: (state: S, localState: Record<string, any>) => VNode<S>[];
56
+ afterRender?: (props: {
59
57
  state: S;
60
58
  localState: Record<string, any>;
61
- }) => VNode<S>[];
59
+ }, vnode: VNode<S>) => VNode<S>;
60
+ [key: string]: any;
61
+ }) => VNode<S>;
62
+ /**
63
+ * 検索結果
64
+ *
65
+ * - item
66
+ * ヒットしたアイテム
67
+ *
68
+ * - depth
69
+ * current からの深度
70
+ */
71
+ export interface SearchResult {
72
+ item: NavigatorItem;
73
+ depth: number;
74
+ }
75
+ /**
76
+ * ファイルサーチ - NavigatorFinder プラグイン
77
+ *
78
+ * - 必須項目
79
+ * state, id, currentKeys, searchResult, hitTest, maxItemsCount
80
+ *
81
+ * - searchResult
82
+ * カードとして表示するVNode
83
+ *
84
+ * - hitTest
85
+ * 抽出条件
86
+ *
87
+ * - maxItemsCount
88
+ * 最初に表示させるカードの最大数
89
+ *
90
+ * - 拡張項目
91
+ * afterRender
92
+ *
93
+ * - afterRender
94
+ * レンダーフック
95
+ */
96
+ export declare const NavigatorSearch: <S>(props: {
97
+ state: S;
98
+ id: string;
99
+ currentKeys: Keys_NavigatorItem;
100
+ searchResult: (item: NavigatorItem, depth: number) => VNode<S> | VNode<S>[];
101
+ hitTest: (item: NavigatorItem) => boolean;
102
+ maxItemsCount: number;
62
103
  afterRender?: (props: {
63
104
  state: S;
64
105
  localState: Record<string, any>;
@@ -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
319
  return afterRender ? afterRender({ state, localState }, vnode) : vnode;
328
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;
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.23",
4
4
  "description": "UI foundation library for Hyperapp by is4416",
5
5
  "license": "MIT",
6
6
  "type": "module",