@gjermundgaraba-designs/pixel-paper 0.20.1 → 0.22.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.
Files changed (74) hide show
  1. package/DESIGN.md +21 -15
  2. package/dist/src/bench/bench.d.ts +14 -0
  3. package/dist/src/bench/bench.d.ts.map +1 -0
  4. package/dist/src/bench/bench.js +9 -0
  5. package/dist/src/bench/index.d.ts +3 -0
  6. package/dist/src/bench/index.d.ts.map +1 -0
  7. package/dist/src/bench/index.js +1 -0
  8. package/dist/src/chip/chip.d.ts +10 -0
  9. package/dist/src/chip/chip.d.ts.map +1 -1
  10. package/dist/src/chip/chip.js +19 -0
  11. package/dist/src/chip/index.d.ts +2 -2
  12. package/dist/src/chip/index.d.ts.map +1 -1
  13. package/dist/src/chip/index.js +1 -1
  14. package/dist/src/drag/drag.d.ts +18 -0
  15. package/dist/src/drag/drag.d.ts.map +1 -1
  16. package/dist/src/drag/drag.js +35 -7
  17. package/dist/src/field/field.d.ts +3 -0
  18. package/dist/src/field/field.d.ts.map +1 -1
  19. package/dist/src/field/field.js +2 -2
  20. package/dist/src/index.d.ts +1 -0
  21. package/dist/src/index.d.ts.map +1 -1
  22. package/dist/src/index.js +1 -0
  23. package/dist/src/lineDiff/index.d.ts +2 -0
  24. package/dist/src/lineDiff/index.d.ts.map +1 -1
  25. package/dist/src/lineDiff/index.js +1 -0
  26. package/dist/src/lineDiff/lineDiff.d.ts +8 -4
  27. package/dist/src/lineDiff/lineDiff.d.ts.map +1 -1
  28. package/dist/src/lineDiff/lineDiff.js +19 -15
  29. package/dist/src/lineDiff/patch.d.ts +35 -0
  30. package/dist/src/lineDiff/patch.d.ts.map +1 -0
  31. package/dist/src/lineDiff/patch.js +146 -0
  32. package/dist/src/menu/menu.d.ts +6 -6
  33. package/dist/src/rail/index.d.ts +1 -1
  34. package/dist/src/rail/index.d.ts.map +1 -1
  35. package/dist/src/rail/index.js +1 -1
  36. package/dist/src/rail/rail.d.ts +217 -17
  37. package/dist/src/rail/rail.d.ts.map +1 -1
  38. package/dist/src/rail/rail.js +133 -15
  39. package/dist/src/reel/index.d.ts +1 -1
  40. package/dist/src/reel/index.d.ts.map +1 -1
  41. package/dist/src/reel/index.js +1 -1
  42. package/dist/src/reel/layout.d.ts +21 -26
  43. package/dist/src/reel/layout.d.ts.map +1 -1
  44. package/dist/src/reel/layout.js +84 -106
  45. package/dist/src/reel/reel.d.ts +30 -172
  46. package/dist/src/reel/reel.d.ts.map +1 -1
  47. package/dist/src/reel/reel.js +144 -195
  48. package/dist/src/rows/rows.d.ts +9 -1
  49. package/dist/src/rows/rows.d.ts.map +1 -1
  50. package/dist/src/rows/rows.js +1 -1
  51. package/dist/src/sortable/sortable.d.ts +30 -30
  52. package/dist/src/toast/toast.d.ts +12 -12
  53. package/dist/src/window/window.d.ts +3 -1
  54. package/dist/src/window/window.d.ts.map +1 -1
  55. package/package.json +1 -1
  56. package/src/bench/bench.ts +24 -0
  57. package/src/bench/index.ts +2 -0
  58. package/src/chip/chip.ts +31 -0
  59. package/src/chip/index.ts +2 -2
  60. package/src/drag/drag.ts +60 -1
  61. package/src/field/field.ts +5 -1
  62. package/src/index.ts +1 -0
  63. package/src/lineDiff/index.ts +2 -0
  64. package/src/lineDiff/lineDiff.ts +29 -24
  65. package/src/lineDiff/patch.ts +218 -0
  66. package/src/pixel-paper.css +202 -32
  67. package/src/rail/index.ts +2 -0
  68. package/src/rail/rail.ts +181 -16
  69. package/src/reel/index.ts +0 -1
  70. package/src/reel/layout.ts +78 -118
  71. package/src/reel/reel.ts +108 -216
  72. package/src/rows/rows.ts +14 -2
  73. package/src/tokens.css +2 -1
  74. package/src/window/window.ts +3 -1
@@ -47,7 +47,8 @@ export type TreegridPlace = Readonly<{
47
47
  export type RowConfig<Message> = Readonly<{
48
48
  /** The row's stable id among its siblings. */
49
49
  key: string;
50
- /** The 18px glyph slot at the left: a `glyphView`, or a `toggleView`. */
50
+ /** The 18px glyph slot at the left: a `glyphView`, a `toggleView`, or a status as a
51
+ * `Chip.dotView`. */
51
52
  glyph?: Html;
52
53
  /** The row's text; it takes the free width and ellipsises. */
53
54
  body: ReadonlyArray<Html | string>;
@@ -68,6 +69,13 @@ export type RowConfig<Message> = Readonly<{
68
69
  * after its text for screen readers, which the muting does not reach. In a selectable
69
70
  * row the text names the box, so the box's name says it too. */
70
71
  isContext?: boolean;
72
+ /** The row's text holds one link (a task, a page), and the row's whole box is its target: a
73
+ * press anywhere on the row follows it, while the row's own controls (the box, a toggle,
74
+ * the actions and tools) and whatever has a tip stay their own targets. The row takes no
75
+ * Tab stop of its own, and the link's ring is drawn round the row. A link that is
76
+ * `aria-current` inverts the row. For a list no keyboard drives: a treegrid's row (with
77
+ * `treegrid`) is already its own stop, and ignores it. */
78
+ isLink?: boolean;
71
79
  /** Makes the row selectable: a box at its left edge. */
72
80
  select?: RowSelect<Message>;
73
81
  /** The row's place in a treegrid; every row of a treegrid has one. */
@@ -1 +1 @@
1
- {"version":3,"file":"rows.d.ts","sourceRoot":"","sources":["../../../src/rows/rows.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"rows.d.ts","sourceRoot":"","sources":["../../../src/rows/rows.ts"],"names":[],"mappings":"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"}
@@ -29,7 +29,7 @@ const textOf = (config, h) => __foldkitBrandViewResult((config.isContext === tru
29
29
  const lineView = (config, box, text, h) => __foldkitBrandViewResult((h.div([
30
30
  ...(config.attributes ?? []),
31
31
  ...rowRole(config.treegrid, h),
32
- h.Class(config.isContext === true ? "row context" : "row"),
32
+ h.Class(`row${config.isContext === true ? " context" : ""}${config.isLink === true && config.treegrid === undefined ? " linked" : ""}`),
33
33
  ], [
34
34
  ...box,
35
35
  ...cellView(config, "cell", config.glyph === undefined ? [] : [config.glyph], h),
@@ -245,8 +245,6 @@ export declare const update: (model: Model, message: Message) => Readonly<{
245
245
  }>;
246
246
  };
247
247
  commands?: Update.Commands<{
248
- readonly _tag: "PressedEscape";
249
- } | {
250
248
  readonly _tag: "CancelledPointer";
251
249
  } | {
252
250
  readonly _tag: "CompletedAnnounceReorder";
@@ -255,9 +253,6 @@ export declare const update: (model: Model, message: Message) => Readonly<{
255
253
  } | {
256
254
  readonly _tag: "GotDragAndDropMessage";
257
255
  readonly message: {
258
- readonly _tag: "PressedArrowKey";
259
- readonly direction: "Down" | "Left" | "NextContainer" | "PreviousContainer" | "Right" | "Up";
260
- } | {
261
256
  readonly _tag: "ActivatedKeyboardDrag";
262
257
  readonly itemId: string;
263
258
  readonly containerId: string;
@@ -284,6 +279,9 @@ export declare const update: (model: Model, message: Message) => Readonly<{
284
279
  readonly containerId: string;
285
280
  readonly index: number;
286
281
  }>;
282
+ } | {
283
+ readonly _tag: "PressedArrowKey";
284
+ readonly direction: "Down" | "Left" | "NextContainer" | "PreviousContainer" | "Right" | "Up";
287
285
  } | {
288
286
  readonly _tag: "PressedDraggable";
289
287
  readonly itemId: string;
@@ -301,6 +299,8 @@ export declare const update: (model: Model, message: Message) => Readonly<{
301
299
  readonly clientX: number;
302
300
  readonly clientY: number;
303
301
  readonly maybeIndex: Option.Option<number>;
302
+ } | {
303
+ readonly _tag: "PressedEscape";
304
304
  } | {
305
305
  readonly _tag: "PressedGripKey";
306
306
  readonly itemId: string;
@@ -413,8 +413,6 @@ export declare const subscriptions: <Prefix extends string>(keyPrefix: Prefix) =
413
413
  }>;
414
414
  }>;
415
415
  }, {
416
- readonly _tag: "PressedEscape";
417
- } | {
418
416
  readonly _tag: "CancelledPointer";
419
417
  } | {
420
418
  readonly _tag: "CompletedAnnounceReorder";
@@ -423,9 +421,6 @@ export declare const subscriptions: <Prefix extends string>(keyPrefix: Prefix) =
423
421
  } | {
424
422
  readonly _tag: "GotDragAndDropMessage";
425
423
  readonly message: {
426
- readonly _tag: "PressedArrowKey";
427
- readonly direction: "Down" | "Left" | "NextContainer" | "PreviousContainer" | "Right" | "Up";
428
- } | {
429
424
  readonly _tag: "ActivatedKeyboardDrag";
430
425
  readonly itemId: string;
431
426
  readonly containerId: string;
@@ -452,6 +447,9 @@ export declare const subscriptions: <Prefix extends string>(keyPrefix: Prefix) =
452
447
  readonly containerId: string;
453
448
  readonly index: number;
454
449
  }>;
450
+ } | {
451
+ readonly _tag: "PressedArrowKey";
452
+ readonly direction: "Down" | "Left" | "NextContainer" | "PreviousContainer" | "Right" | "Up";
455
453
  } | {
456
454
  readonly _tag: "PressedDraggable";
457
455
  readonly itemId: string;
@@ -469,6 +467,8 @@ export declare const subscriptions: <Prefix extends string>(keyPrefix: Prefix) =
469
467
  readonly clientX: number;
470
468
  readonly clientY: number;
471
469
  readonly maybeIndex: Option.Option<number>;
470
+ } | {
471
+ readonly _tag: "PressedEscape";
472
472
  } | {
473
473
  readonly _tag: "PressedGripKey";
474
474
  readonly itemId: string;
@@ -559,8 +559,6 @@ export declare const subscriptions: <Prefix extends string>(keyPrefix: Prefix) =
559
559
  }>;
560
560
  }>;
561
561
  }, {
562
- readonly _tag: "PressedEscape";
563
- } | {
564
562
  readonly _tag: "CancelledPointer";
565
563
  } | {
566
564
  readonly _tag: "CompletedAnnounceReorder";
@@ -569,9 +567,6 @@ export declare const subscriptions: <Prefix extends string>(keyPrefix: Prefix) =
569
567
  } | {
570
568
  readonly _tag: "GotDragAndDropMessage";
571
569
  readonly message: {
572
- readonly _tag: "PressedArrowKey";
573
- readonly direction: "Down" | "Left" | "NextContainer" | "PreviousContainer" | "Right" | "Up";
574
- } | {
575
570
  readonly _tag: "ActivatedKeyboardDrag";
576
571
  readonly itemId: string;
577
572
  readonly containerId: string;
@@ -598,6 +593,9 @@ export declare const subscriptions: <Prefix extends string>(keyPrefix: Prefix) =
598
593
  readonly containerId: string;
599
594
  readonly index: number;
600
595
  }>;
596
+ } | {
597
+ readonly _tag: "PressedArrowKey";
598
+ readonly direction: "Down" | "Left" | "NextContainer" | "PreviousContainer" | "Right" | "Up";
601
599
  } | {
602
600
  readonly _tag: "PressedDraggable";
603
601
  readonly itemId: string;
@@ -615,6 +613,8 @@ export declare const subscriptions: <Prefix extends string>(keyPrefix: Prefix) =
615
613
  readonly clientX: number;
616
614
  readonly clientY: number;
617
615
  readonly maybeIndex: Option.Option<number>;
616
+ } | {
617
+ readonly _tag: "PressedEscape";
618
618
  } | {
619
619
  readonly _tag: "PressedGripKey";
620
620
  readonly itemId: string;
@@ -710,8 +710,6 @@ export declare const subscriptions: <Prefix extends string>(keyPrefix: Prefix) =
710
710
  }>;
711
711
  }>;
712
712
  }, {
713
- readonly _tag: "PressedEscape";
714
- } | {
715
713
  readonly _tag: "CancelledPointer";
716
714
  } | {
717
715
  readonly _tag: "CompletedAnnounceReorder";
@@ -720,9 +718,6 @@ export declare const subscriptions: <Prefix extends string>(keyPrefix: Prefix) =
720
718
  } | {
721
719
  readonly _tag: "GotDragAndDropMessage";
722
720
  readonly message: {
723
- readonly _tag: "PressedArrowKey";
724
- readonly direction: "Down" | "Left" | "NextContainer" | "PreviousContainer" | "Right" | "Up";
725
- } | {
726
721
  readonly _tag: "ActivatedKeyboardDrag";
727
722
  readonly itemId: string;
728
723
  readonly containerId: string;
@@ -749,6 +744,9 @@ export declare const subscriptions: <Prefix extends string>(keyPrefix: Prefix) =
749
744
  readonly containerId: string;
750
745
  readonly index: number;
751
746
  }>;
747
+ } | {
748
+ readonly _tag: "PressedArrowKey";
749
+ readonly direction: "Down" | "Left" | "NextContainer" | "PreviousContainer" | "Right" | "Up";
752
750
  } | {
753
751
  readonly _tag: "PressedDraggable";
754
752
  readonly itemId: string;
@@ -766,6 +764,8 @@ export declare const subscriptions: <Prefix extends string>(keyPrefix: Prefix) =
766
764
  readonly clientX: number;
767
765
  readonly clientY: number;
768
766
  readonly maybeIndex: Option.Option<number>;
767
+ } | {
768
+ readonly _tag: "PressedEscape";
769
769
  } | {
770
770
  readonly _tag: "PressedGripKey";
771
771
  readonly itemId: string;
@@ -857,8 +857,6 @@ export declare const subscriptions: <Prefix extends string>(keyPrefix: Prefix) =
857
857
  }>;
858
858
  }>;
859
859
  }, {
860
- readonly _tag: "PressedEscape";
861
- } | {
862
860
  readonly _tag: "CancelledPointer";
863
861
  } | {
864
862
  readonly _tag: "CompletedAnnounceReorder";
@@ -867,9 +865,6 @@ export declare const subscriptions: <Prefix extends string>(keyPrefix: Prefix) =
867
865
  } | {
868
866
  readonly _tag: "GotDragAndDropMessage";
869
867
  readonly message: {
870
- readonly _tag: "PressedArrowKey";
871
- readonly direction: "Down" | "Left" | "NextContainer" | "PreviousContainer" | "Right" | "Up";
872
- } | {
873
868
  readonly _tag: "ActivatedKeyboardDrag";
874
869
  readonly itemId: string;
875
870
  readonly containerId: string;
@@ -896,6 +891,9 @@ export declare const subscriptions: <Prefix extends string>(keyPrefix: Prefix) =
896
891
  readonly containerId: string;
897
892
  readonly index: number;
898
893
  }>;
894
+ } | {
895
+ readonly _tag: "PressedArrowKey";
896
+ readonly direction: "Down" | "Left" | "NextContainer" | "PreviousContainer" | "Right" | "Up";
899
897
  } | {
900
898
  readonly _tag: "PressedDraggable";
901
899
  readonly itemId: string;
@@ -913,6 +911,8 @@ export declare const subscriptions: <Prefix extends string>(keyPrefix: Prefix) =
913
911
  readonly clientX: number;
914
912
  readonly clientY: number;
915
913
  readonly maybeIndex: Option.Option<number>;
914
+ } | {
915
+ readonly _tag: "PressedEscape";
916
916
  } | {
917
917
  readonly _tag: "PressedGripKey";
918
918
  readonly itemId: string;
@@ -1055,8 +1055,6 @@ export declare const view: import("foldkit/submodel").View<{
1055
1055
  }>;
1056
1056
  }>;
1057
1057
  }, {
1058
- readonly _tag: "PressedEscape";
1059
- } | {
1060
1058
  readonly _tag: "CancelledPointer";
1061
1059
  } | {
1062
1060
  readonly _tag: "CompletedAnnounceReorder";
@@ -1065,9 +1063,6 @@ export declare const view: import("foldkit/submodel").View<{
1065
1063
  } | {
1066
1064
  readonly _tag: "GotDragAndDropMessage";
1067
1065
  readonly message: {
1068
- readonly _tag: "PressedArrowKey";
1069
- readonly direction: "Down" | "Left" | "NextContainer" | "PreviousContainer" | "Right" | "Up";
1070
- } | {
1071
1066
  readonly _tag: "ActivatedKeyboardDrag";
1072
1067
  readonly itemId: string;
1073
1068
  readonly containerId: string;
@@ -1094,6 +1089,9 @@ export declare const view: import("foldkit/submodel").View<{
1094
1089
  readonly containerId: string;
1095
1090
  readonly index: number;
1096
1091
  }>;
1092
+ } | {
1093
+ readonly _tag: "PressedArrowKey";
1094
+ readonly direction: "Down" | "Left" | "NextContainer" | "PreviousContainer" | "Right" | "Up";
1097
1095
  } | {
1098
1096
  readonly _tag: "PressedDraggable";
1099
1097
  readonly itemId: string;
@@ -1111,6 +1109,8 @@ export declare const view: import("foldkit/submodel").View<{
1111
1109
  readonly clientX: number;
1112
1110
  readonly clientY: number;
1113
1111
  readonly maybeIndex: Option.Option<number>;
1112
+ } | {
1113
+ readonly _tag: "PressedEscape";
1114
1114
  } | {
1115
1115
  readonly _tag: "PressedGripKey";
1116
1116
  readonly itemId: string;
@@ -326,6 +326,8 @@ export declare const update: (model: Model, message: Message) => Readonly<{
326
326
  readonly pendingDismissVersion: number;
327
327
  readonly isHovered: boolean;
328
328
  readonly swipeState: {
329
+ readonly _tag: "Idle";
330
+ } | {
329
331
  readonly _tag: "Dismissing";
330
332
  readonly offsetX: number;
331
333
  readonly direction: "Left" | "Right";
@@ -334,8 +336,6 @@ export declare const update: (model: Model, message: Message) => Readonly<{
334
336
  readonly pointerId: number;
335
337
  readonly startX: number;
336
338
  readonly currentX: number;
337
- } | {
338
- readonly _tag: "Idle";
339
339
  } | {
340
340
  readonly _tag: "Settling";
341
341
  readonly offsetX: number;
@@ -371,15 +371,15 @@ export declare const update: (model: Model, message: Message) => Readonly<{
371
371
  readonly _tag: "GotAnimationMessage";
372
372
  readonly entryId: string;
373
373
  readonly message: {
374
+ readonly _tag: "Hid";
375
+ } | {
376
+ readonly _tag: "Showed";
377
+ } | {
374
378
  readonly _tag: "CompletedWaitForPaint";
375
379
  readonly generation: number;
376
380
  } | {
377
381
  readonly _tag: "EndedAnimation";
378
382
  readonly generation: number;
379
- } | {
380
- readonly _tag: "Hid";
381
- } | {
382
- readonly _tag: "Showed";
383
383
  };
384
384
  } | {
385
385
  readonly _tag: "HoveredEntry";
@@ -557,6 +557,8 @@ export declare const view: import("foldkit/submodel").View<{
557
557
  readonly pendingDismissVersion: number;
558
558
  readonly isHovered: boolean;
559
559
  readonly swipeState: {
560
+ readonly _tag: "Idle";
561
+ } | {
560
562
  readonly _tag: "Dismissing";
561
563
  readonly offsetX: number;
562
564
  readonly direction: "Left" | "Right";
@@ -565,8 +567,6 @@ export declare const view: import("foldkit/submodel").View<{
565
567
  readonly pointerId: number;
566
568
  readonly startX: number;
567
569
  readonly currentX: number;
568
- } | {
569
- readonly _tag: "Idle";
570
570
  } | {
571
571
  readonly _tag: "Settling";
572
572
  readonly offsetX: number;
@@ -602,15 +602,15 @@ export declare const view: import("foldkit/submodel").View<{
602
602
  readonly _tag: "GotAnimationMessage";
603
603
  readonly entryId: string;
604
604
  readonly message: {
605
+ readonly _tag: "Hid";
606
+ } | {
607
+ readonly _tag: "Showed";
608
+ } | {
605
609
  readonly _tag: "CompletedWaitForPaint";
606
610
  readonly generation: number;
607
611
  } | {
608
612
  readonly _tag: "EndedAnimation";
609
613
  readonly generation: number;
610
- } | {
611
- readonly _tag: "Hid";
612
- } | {
613
- readonly _tag: "Showed";
614
614
  };
615
615
  } | {
616
616
  readonly _tag: "HoveredEntry";
@@ -12,7 +12,9 @@ export type ViewConfig<Message> = Readonly<{
12
12
  glyph?: GlyphName;
13
13
  /** A chip in the title bar: its state. */
14
14
  status?: ReadonlyArray<Html | string>;
15
- /** IconButtons in the title bar, before the close. */
15
+ /** IconButtons in the title bar, before the close. A child Submodel, whose view is one
16
+ * `Html`, hands back several in a `span.window-tools`: it is no box of its own
17
+ * (`display: contents`), so its buttons sit in the bar as the others do. */
16
18
  tools?: ReadonlyArray<Html>;
17
19
  /** The close glyph's Message; the glyph prints when it is given. */
18
20
  onClose?: Message;
@@ -1 +1 @@
1
- {"version":3,"file":"window.d.ts","sourceRoot":"","sources":["../../../src/window/window.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAGtD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAOpD,wDAAwD;AACxD,MAAM,MAAM,UAAU,CAAC,OAAO,IAAI,QAAQ,CAAC;IACzC,oFAAoF;IACpF,EAAE,EAAE,MAAM,CAAC;IACX,4CAA4C;IAC5C,KAAK,EAAE,MAAM,CAAC;IACd,+DAA+D;IAC/D,IAAI,CAAC,EAAE,aAAa,CAAC,IAAI,CAAC,CAAC;IAC3B,+DAA+D;IAC/D,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,0CAA0C;IAC1C,MAAM,CAAC,EAAE,aAAa,CAAC,IAAI,GAAG,MAAM,CAAC,CAAC;IACtC,sDAAsD;IACtD,KAAK,CAAC,EAAE,aAAa,CAAC,IAAI,CAAC,CAAC;IAC5B,oEAAoE;IACpE,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,IAAI,EAAE,aAAa,CAAC,IAAI,GAAG,MAAM,CAAC,CAAC;IACnC,iEAAiE;IACjE,IAAI,CAAC,EAAE,aAAa,CAAC,IAAI,GAAG,MAAM,CAAC,CAAC;IACpC,8DAA8D;IAC9D,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB,CAAC,CAAC;AAEH;;6DAE6D;AAC7D,eAAO,MAAM,IAAI,GAAI,OAAO,UAAU,UAAU,CAAC,OAAO,CAAC,KAAK,WAAW,CAAC,OAAO,CAAC,KAAG,IAsCpF,CAAC"}
1
+ {"version":3,"file":"window.d.ts","sourceRoot":"","sources":["../../../src/window/window.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAGtD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAOpD,wDAAwD;AACxD,MAAM,MAAM,UAAU,CAAC,OAAO,IAAI,QAAQ,CAAC;IACzC,oFAAoF;IACpF,EAAE,EAAE,MAAM,CAAC;IACX,4CAA4C;IAC5C,KAAK,EAAE,MAAM,CAAC;IACd,+DAA+D;IAC/D,IAAI,CAAC,EAAE,aAAa,CAAC,IAAI,CAAC,CAAC;IAC3B,+DAA+D;IAC/D,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,0CAA0C;IAC1C,MAAM,CAAC,EAAE,aAAa,CAAC,IAAI,GAAG,MAAM,CAAC,CAAC;IACtC;;iFAE6E;IAC7E,KAAK,CAAC,EAAE,aAAa,CAAC,IAAI,CAAC,CAAC;IAC5B,oEAAoE;IACpE,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,IAAI,EAAE,aAAa,CAAC,IAAI,GAAG,MAAM,CAAC,CAAC;IACnC,iEAAiE;IACjE,IAAI,CAAC,EAAE,aAAa,CAAC,IAAI,GAAG,MAAM,CAAC,CAAC;IACpC,8DAA8D;IAC9D,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB,CAAC,CAAC;AAEH;;6DAE6D;AAC7D,eAAO,MAAM,IAAI,GAAI,OAAO,UAAU,UAAU,CAAC,OAAO,CAAC,KAAK,WAAW,CAAC,OAAO,CAAC,KAAG,IAsCpF,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gjermundgaraba-designs/pixel-paper",
3
- "version": "0.20.1",
3
+ "version": "0.22.0",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
@@ -0,0 +1,24 @@
1
+ import type { Html, HtmlBuilder } from "foldkit/html";
2
+
3
+ // VIEW
4
+
5
+ /** Configuration for rendering a bench with `view`. */
6
+ export type ViewConfig = Readonly<{
7
+ /** Above the work, at its own height: a PageHead, notices, and a Rail's open button first. */
8
+ head: ReadonlyArray<Html | string>;
9
+ /** The work, taking the rest of the window's height: a Reel, which fills it. */
10
+ body: ReadonlyArray<Html | string>;
11
+ }>;
12
+
13
+ /** A page whose work is the window: its head at its own height, then the work in all the
14
+ * height left, from under the top bar to the window's bottom and edge to edge (beside a Rail,
15
+ * from the rail's edge). The main landmark (`<main id="main">`). The page does not scroll;
16
+ * the work scrolls itself where it must (a reel's columns, sideways). */
17
+ export const view = <Message>(config: ViewConfig, h: HtmlBuilder<Message>): Html => {
18
+ const { head, body } = config;
19
+
20
+ return h.main(
21
+ [h.Id("main"), h.Class("bench")],
22
+ [h.div([h.Class("bench-head")], head), h.div([h.Class("bench-work")], body)],
23
+ );
24
+ };
@@ -0,0 +1,2 @@
1
+ export { view } from "./bench.ts";
2
+ export type { ViewConfig } from "./bench.ts";
package/src/chip/chip.ts CHANGED
@@ -37,3 +37,34 @@ export const view = <Message>(config: ViewConfig, h: HtmlBuilder<Message>): Html
37
37
  [label, ...(isBusy ? [h.span([h.Class("cursor"), h.AriaHidden(true)])] : [])],
38
38
  );
39
39
  };
40
+
41
+ // DOT
42
+
43
+ /** Configuration for rendering a dot with `dotView`. */
44
+ export type DotConfig = Readonly<{
45
+ /** The state word: the dot's accessible name and its tip. */
46
+ label: string;
47
+ tone?: Tone;
48
+ }>;
49
+
50
+ const DOT_CLASS: Readonly<Record<Tone, string>> = {
51
+ Plain: "chip-dot",
52
+ Go: "chip-dot go",
53
+ Warn: "chip-dot warn",
54
+ Blocked: "chip-dot blocked",
55
+ Done: "chip-dot done",
56
+ };
57
+
58
+ /** A chip as a 10px cell in its tone, where a word would take the room a title needs (a task
59
+ * row's status, before its title). It is an image (`role="img"`) named by the word, which its
60
+ * tip shows, so the state never rests on its colour. */
61
+ export const dotView = <Message>(config: DotConfig, h: HtmlBuilder<Message>): Html => {
62
+ const { label, tone = "Plain" } = config;
63
+
64
+ return h.span([
65
+ h.Role("img"),
66
+ h.AriaLabel(label),
67
+ h.DataAttribute("tip", label),
68
+ h.Class(DOT_CLASS[tone]),
69
+ ]);
70
+ };
package/src/chip/index.ts CHANGED
@@ -1,2 +1,2 @@
1
- export { view } from "./chip.ts";
2
- export type { Tone, ViewConfig } from "./chip.ts";
1
+ export { dotView, view } from "./chip.ts";
2
+ export type { DotConfig, Tone, ViewConfig } from "./chip.ts";
package/src/drag/drag.ts CHANGED
@@ -1,11 +1,15 @@
1
1
  // Shared by Board and Sortable, the two users of @foldkit/ui's DragAndDrop, and Reel, which
2
2
  // holds its own: the phase a drag is in, the DragAndDrop Messages both build, what a press
3
3
  // lifts, Escape taken before anything else, the pressing pointer's events, the lifted copy's
4
- // measure, and the closest-centre drop target a sortable resolves for a pointer.
4
+ // measure, and the closest-centre drop target a sortable resolves for a pointer. A splitter's
5
+ // handlers are the Reel's and the Rail's.
5
6
 
6
7
  import { DragAndDrop as UiDragAndDrop } from "@foldkit/ui";
7
8
  import { Array, Effect, Option, Order, Stream, pipe } from "effect";
8
9
  import * as Dom from "foldkit/dom";
10
+ import type { Attribute, HtmlBuilder } from "foldkit/html";
11
+
12
+ import { isPlain } from "../keys/keys.ts";
9
13
 
10
14
  // PHASE
11
15
 
@@ -125,6 +129,61 @@ export const pressingPointer = (pointerId: number): Stream.Stream<PointerEvent>
125
129
  (event) => event.pointerId === pointerId,
126
130
  );
127
131
 
132
+ /** What the pressing pointer `pointerId` does, as Messages: each move, the release and the
133
+ * browser's cancel. */
134
+ export const pointerMessages = <Message>(
135
+ pointerId: number,
136
+ toMessage: Readonly<{
137
+ moved: (event: PointerEvent) => Message;
138
+ released: (event: PointerEvent) => Message;
139
+ cancelled: () => Message;
140
+ }>,
141
+ ): Stream.Stream<Message> =>
142
+ Stream.map(pressingPointer(pointerId), (event) => {
143
+ if (event.type === "pointermove") {
144
+ return toMessage.moved(event);
145
+ } else if (event.type === "pointerup") {
146
+ return toMessage.released(event);
147
+ } else {
148
+ return toMessage.cancelled();
149
+ }
150
+ });
151
+
152
+ // SPLITTER
153
+
154
+ /** A splitter's handlers. The main button of a pointer presses it (`onPress`, while
155
+ * `isIdle`), and its arrows, unmodified, step it (`onStep`, one way or the other): the side
156
+ * arrows across a `"vertical"` splitter (it sizes a width), up and down along a
157
+ * `"horizontal"` one (a height). A modified arrow is the application's. */
158
+ export const splitterAttributes = <Message>(
159
+ config: Readonly<{
160
+ orientation: "vertical" | "horizontal";
161
+ isIdle: boolean;
162
+ onPress: (pointerId: number, clientX: number, clientY: number) => Message;
163
+ onStep: (direction: -1 | 1) => Message;
164
+ }>,
165
+ h: HtmlBuilder<Message>,
166
+ ): ReadonlyArray<Attribute<Message>> => {
167
+ const [less, more] =
168
+ config.orientation === "vertical" ? ["ArrowLeft", "ArrowRight"] : ["ArrowUp", "ArrowDown"];
169
+ return [
170
+ h.AriaOrientation(config.orientation),
171
+ h.OnPointerDown(
172
+ (_pointerType, button, _screenX, _screenY, _time, clientX, clientY, pointerId) =>
173
+ Option.liftPredicate(
174
+ config.onPress(pointerId, clientX, clientY),
175
+ () => button === LEFT_BUTTON && config.isIdle,
176
+ ),
177
+ ),
178
+ h.OnKeyDownPreventDefault((key, modifiers) =>
179
+ Option.liftPredicate(
180
+ config.onStep(key === more ? 1 : -1),
181
+ () => isPlain(modifiers) && (key === less || key === more),
182
+ ),
183
+ ),
184
+ ];
185
+ };
186
+
128
187
  // LIFTED COPY
129
188
 
130
189
  /** Sizes the copy that follows the pointer to the element `sourceSelector` finds, and keeps the
@@ -48,6 +48,9 @@ export type ViewConfig<Message> = Readonly<{
48
48
  isBoxed?: boolean;
49
49
  /** Code and identifiers: the system monospace. */
50
50
  isMono?: boolean;
51
+ /** The label is for screen readers only (`.sr-only`): a field whose place says what it is,
52
+ * such as a browser's address bar. It still names the input. */
53
+ isLabelHidden?: boolean;
51
54
  /** The input's type (`"date"` for the browser's date control, always boxed); text when not
52
55
  * given. */
53
56
  type?: string;
@@ -71,6 +74,7 @@ export const view = <Message>(config: ViewConfig<Message>, h: HtmlBuilder<Messag
71
74
  isRequired = false,
72
75
  isBoxed = false,
73
76
  isMono = false,
77
+ isLabelHidden = false,
74
78
  type,
75
79
  inputAttributes = [],
76
80
  } = config;
@@ -87,7 +91,7 @@ export const view = <Message>(config: ViewConfig<Message>, h: HtmlBuilder<Messag
87
91
  h.div(
88
92
  [h.Class(`field${isBoxed || type === "date" ? " boxed" : ""}${isMono ? " mono" : ""}`)],
89
93
  [
90
- h.label(groups.label, [label]),
94
+ h.label([...groups.label, ...(isLabelHidden ? [h.Class("sr-only")] : [])], [label]),
91
95
  // NOTE: @foldkit/ui gap: Input has no required option, so the wrapper says it.
92
96
  h.input([
93
97
  ...inputAttributes,
package/src/index.ts CHANGED
@@ -2,6 +2,7 @@ export * as Announcer from "./announcer/index.ts";
2
2
  export * as AudioFrame from "./audioFrame/index.ts";
3
3
  export * as Banner from "./banner/index.ts";
4
4
  export * as BarChart from "./barChart/index.ts";
5
+ export * as Bench from "./bench/index.ts";
5
6
  export * as Board from "./board/index.ts";
6
7
  export * as Button from "./button/index.ts";
7
8
  export * as Card from "./card/index.ts";
@@ -1,2 +1,4 @@
1
1
  export { patchView, view } from "./lineDiff.ts";
2
2
  export type { PatchViewConfig, ViewConfig } from "./lineDiff.ts";
3
+ export { describePatch } from "./patch.ts";
4
+ export type { PatchDescription } from "./patch.ts";
@@ -1,15 +1,9 @@
1
- import { Array, Option, String } from "effect";
1
+ import { Array, Option } from "effect";
2
2
  import type { Html, HtmlBuilder } from "foldkit/html";
3
3
 
4
4
  import { LOCALE } from "../locale/locale.ts";
5
- import {
6
- DiffRow,
7
- type LineKind,
8
- diffRowsOf,
9
- diffRowsOfPatch,
10
- foldUnchanged,
11
- hasChanges,
12
- } from "./diff.ts";
5
+ import { DiffRow, type LineKind, diffRowsOf, foldUnchanged, hasChanges } from "./diff.ts";
6
+ import { SAME_NOTE, readPatch } from "./patch.ts";
13
7
 
14
8
  // VIEW
15
9
 
@@ -29,7 +23,8 @@ export type ViewConfig = Readonly<{
29
23
 
30
24
  /** Configuration for rendering a unified diff with `patchView`. */
31
25
  export type PatchViewConfig = Readonly<{
32
- /** One file's unified diff, as `diff -u` or `git diff -- <path>` writes it. */
26
+ /** One file's unified diff, as `diff -u` or `git diff -- <path>` writes it, its git header
27
+ * included. */
33
28
  patch: string;
34
29
  /** Names the two sides for screen readers: `"Version 3 against version 4"`. */
35
30
  label: string;
@@ -86,8 +81,6 @@ const rowView = <Message>(row: DiffRow, h: HtmlBuilder<Message>): Html =>
86
81
  const noteView = <Message>(note: string, h: HtmlBuilder<Message>): Html =>
87
82
  h.p([h.Class("note-line")], [note]);
88
83
 
89
- const SAME_NOTE = "No difference: both sides hold the same text.";
90
-
91
84
  const tableView = <Message>(
92
85
  rows: ReadonlyArray<DiffRow>,
93
86
  label: string,
@@ -123,18 +116,30 @@ export const view = <Message>(config: ViewConfig, h: HtmlBuilder<Message>): Html
123
116
  /** A unified diff the caller already has (from a server, or `git diff -- <path>`) as `view`'s
124
117
  * table: its hunks' lines, numbered from their headers, and the unchanged lines between hunks
125
118
  * folded into a counted row. Lines past the last hunk are not in the patch, so nothing counts
126
- * them. An empty patch says there is no difference, as `view` does; anything but one file
127
- * with a hunk (text that is not a diff, a binary change, several files) says it cannot be
128
- * shown as lines. */
119
+ * them. What git's header says beyond the lines prints as a note line above the table, or in
120
+ * its place: a rename, a mode change, an empty or binary file (`describePatch` has the
121
+ * sentences). A file whose type changed (a file that became a symlink) is two sections, each
122
+ * its own table under one note, named for its side (`"<label>, as it was"`). An empty patch
123
+ * says there is no difference, as `view` does; text that is not a diff, or holds several
124
+ * files, says it cannot be shown as lines. */
129
125
  export const patchView = <Message>(config: PatchViewConfig, h: HtmlBuilder<Message>): Html => {
130
126
  const { patch, label } = config;
131
-
132
- if (String.isEmpty(String.trim(patch))) {
133
- return noteView(SAME_NOTE, h);
134
- } else {
135
- return Option.match(diffRowsOfPatch(patch), {
136
- onNone: () => noteView("This diff cannot be shown as lines.", h),
137
- onSome: (rows) => tableView(rows, label, h),
138
- });
139
- }
127
+ const { notes, tables } = readPatch(patch);
128
+ const parts = [
129
+ ...(Array.isReadonlyArrayNonEmpty(notes) ? [noteView(Array.join(notes, " "), h)] : []),
130
+ ...Array.map(tables, ({ rows, maybeSide }) =>
131
+ tableView(
132
+ rows,
133
+ Option.match(maybeSide, { onNone: () => label, onSome: (side) => `${label}, ${side}` }),
134
+ h,
135
+ ),
136
+ ),
137
+ ];
138
+
139
+ // a lone table or note is drawn bare, so a flush window's body still meets its table
140
+ return Array.match(parts, {
141
+ onEmpty: () => noteView(SAME_NOTE, h),
142
+ onNonEmpty: ([part, ...others]) =>
143
+ Array.isArrayEmpty(others) ? part : h.div([h.Class("stack gap-s")], parts),
144
+ });
140
145
  };