@gjermundgaraba-designs/pixel-paper 0.22.2 → 0.24.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/DESIGN.md +18 -17
- package/dist/src/adapters/mermaid.d.ts.map +1 -1
- package/dist/src/adapters/mermaid.js +0 -24
- package/dist/src/announcer/announcer.d.ts.map +1 -1
- package/dist/src/announcer/announcer.js +12 -12
- package/dist/src/board/board.d.ts +0 -10
- package/dist/src/board/board.d.ts.map +1 -1
- package/dist/src/board/board.js +36 -64
- package/dist/src/board/index.d.ts +2 -1
- package/dist/src/board/index.d.ts.map +1 -1
- package/dist/src/board/index.js +2 -1
- package/dist/src/button/button.d.ts +1 -1
- package/dist/src/button/button.d.ts.map +1 -1
- package/dist/src/commandPalette/commandPalette.d.ts.map +1 -1
- package/dist/src/commandPalette/commandPalette.js +78 -83
- package/dist/src/commandPalette/keys.d.ts +2 -2
- package/dist/src/commandPalette/keys.d.ts.map +1 -1
- package/dist/src/commandPalette/keys.js +2 -4
- package/dist/src/commandPalette/match.d.ts.map +1 -1
- package/dist/src/commandPalette/match.js +2 -2
- package/dist/src/confirmDialog/confirmDialog.d.ts.map +1 -1
- package/dist/src/confirmDialog/confirmDialog.js +1 -2
- package/dist/src/copyButton/copyButton.d.ts +0 -5
- package/dist/src/copyButton/copyButton.d.ts.map +1 -1
- package/dist/src/copyButton/copyButton.js +2 -2
- package/dist/src/crumbs/crumbs.d.ts +0 -2
- package/dist/src/crumbs/crumbs.d.ts.map +1 -1
- package/dist/src/crumbs/crumbs.js +2 -2
- package/dist/src/dialog/dialog.d.ts +2 -0
- package/dist/src/dialog/dialog.d.ts.map +1 -1
- package/dist/src/dialog/dialog.js +3 -2
- package/dist/src/drag/drag.d.ts +28 -0
- package/dist/src/drag/drag.d.ts.map +1 -1
- package/dist/src/drag/drag.js +39 -17
- package/dist/src/editableTitle/editableTitle.d.ts +4 -4
- package/dist/src/field/field.d.ts +7 -1
- package/dist/src/field/field.d.ts.map +1 -1
- package/dist/src/field/field.js +14 -6
- package/dist/src/fieldset/fieldset.d.ts.map +1 -1
- package/dist/src/fieldset/fieldset.js +2 -4
- package/dist/src/fileTree/fileTree.d.ts.map +1 -1
- package/dist/src/fileTree/fileTree.js +16 -16
- package/dist/src/iconButton/iconButton.d.ts +6 -1
- package/dist/src/iconButton/iconButton.d.ts.map +1 -1
- package/dist/src/iconButton/iconButton.js +7 -2
- package/dist/src/inlineInput/inlineInput.d.ts +4 -0
- package/dist/src/inlineInput/inlineInput.d.ts.map +1 -1
- package/dist/src/inlineInput/inlineInput.js +9 -6
- package/dist/src/keyHelp/index.d.ts +1 -1
- package/dist/src/keyHelp/index.d.ts.map +1 -1
- package/dist/src/keyHelp/index.js +1 -1
- package/dist/src/keyHelp/keyHelp.d.ts +1 -3
- package/dist/src/keyHelp/keyHelp.d.ts.map +1 -1
- package/dist/src/keyHelp/keyHelp.js +3 -6
- package/dist/src/keyHint/keyLabel.d.ts +2 -0
- package/dist/src/keyHint/keyLabel.d.ts.map +1 -1
- package/dist/src/keyHint/keyLabel.js +3 -2
- package/dist/src/lineDiff/diff.d.ts +6 -3
- package/dist/src/lineDiff/diff.d.ts.map +1 -1
- package/dist/src/lineDiff/diff.js +40 -28
- package/dist/src/lineDiff/patch.d.ts.map +1 -1
- package/dist/src/lineDiff/patch.js +18 -13
- package/dist/src/linkState/linkState.d.ts.map +1 -1
- package/dist/src/linkState/linkState.js +1 -7
- package/dist/src/menu/slip.js +2 -2
- package/dist/src/meter/meter.d.ts.map +1 -1
- package/dist/src/meter/meter.js +22 -17
- package/dist/src/notice/notice.d.ts +2 -0
- package/dist/src/notice/notice.d.ts.map +1 -1
- package/dist/src/notice/notice.js +2 -1
- package/dist/src/panel/panel.d.ts +3 -0
- package/dist/src/panel/panel.d.ts.map +1 -1
- package/dist/src/panel/panel.js +2 -1
- package/dist/src/passwordField/passwordField.d.ts.map +1 -1
- package/dist/src/passwordField/passwordField.js +3 -7
- package/dist/src/rail/index.d.ts +1 -1
- package/dist/src/rail/index.d.ts.map +1 -1
- package/dist/src/rail/index.js +1 -1
- package/dist/src/rail/rail.d.ts +95 -20
- package/dist/src/rail/rail.d.ts.map +1 -1
- package/dist/src/rail/rail.js +103 -46
- package/dist/src/reel/layout.d.ts +9 -5
- package/dist/src/reel/layout.d.ts.map +1 -1
- package/dist/src/reel/layout.js +11 -5
- package/dist/src/reel/reel.d.ts.map +1 -1
- package/dist/src/reel/reel.js +122 -68
- package/dist/src/scheme/scheme.d.ts +1 -1
- package/dist/src/scheme/scheme.d.ts.map +1 -1
- package/dist/src/segmented/segmented.d.ts +0 -3
- package/dist/src/segmented/segmented.d.ts.map +1 -1
- package/dist/src/segmented/segmented.js +1 -6
- package/dist/src/selectField/selectField.d.ts.map +1 -1
- package/dist/src/selectField/selectField.js +3 -10
- package/dist/src/sortable/index.d.ts +1 -1
- package/dist/src/sortable/index.d.ts.map +1 -1
- package/dist/src/sortable/index.js +1 -1
- package/dist/src/sortable/sortable.d.ts +39 -40
- package/dist/src/sortable/sortable.d.ts.map +1 -1
- package/dist/src/sortable/sortable.js +14 -31
- package/dist/src/stack/stack.d.ts +1 -1
- package/dist/src/stack/stack.d.ts.map +1 -1
- package/dist/src/stack/stack.js +1 -1
- package/dist/src/tabs/tabs.d.ts +1 -1
- package/dist/src/tabs/tabs.d.ts.map +1 -1
- package/dist/src/tabs/tabs.js +1 -1
- package/dist/src/textareaField/textareaField.d.ts.map +1 -1
- package/dist/src/textareaField/textareaField.js +3 -7
- package/dist/src/time/time.d.ts.map +1 -1
- package/dist/src/time/time.js +4 -2
- package/dist/src/tips/index.d.ts +1 -1
- package/dist/src/tips/index.d.ts.map +1 -1
- package/dist/src/tips/tips.d.ts +30 -1
- package/dist/src/tips/tips.d.ts.map +1 -1
- package/dist/src/tips/tips.js +53 -31
- package/dist/src/toast/index.d.ts +1 -1
- package/dist/src/toast/index.d.ts.map +1 -1
- package/dist/src/toast/toast.d.ts +5 -5
- package/dist/src/toast/toast.d.ts.map +1 -1
- package/dist/src/toast/toast.js +1 -6
- package/dist/src/toolbar/toolbar.d.ts +0 -3
- package/dist/src/toolbar/toolbar.d.ts.map +1 -1
- package/dist/src/toolbar/toolbar.js +1 -2
- package/package.json +1 -1
- package/src/adapters/mermaid.ts +0 -24
- package/src/announcer/announcer.ts +13 -17
- package/src/board/board.ts +32 -80
- package/src/board/index.ts +1 -1
- package/src/button/button.ts +1 -1
- package/src/commandPalette/commandPalette.ts +21 -38
- package/src/commandPalette/keys.ts +2 -4
- package/src/commandPalette/match.ts +3 -3
- package/src/confirmDialog/confirmDialog.ts +5 -3
- package/src/copyButton/copyButton.ts +2 -5
- package/src/crumbs/crumbs.ts +2 -4
- package/src/dialog/dialog.ts +3 -2
- package/src/drag/drag.ts +54 -5
- package/src/field/field.ts +29 -7
- package/src/fieldset/fieldset.ts +2 -4
- package/src/fileTree/fileTree.ts +10 -12
- package/src/iconButton/iconButton.ts +19 -2
- package/src/inlineInput/inlineInput.ts +11 -2
- package/src/keyHelp/index.ts +0 -1
- package/src/keyHelp/keyHelp.ts +3 -9
- package/src/keyHint/keyLabel.ts +5 -2
- package/src/lineDiff/diff.ts +70 -54
- package/src/lineDiff/patch.ts +27 -14
- package/src/linkState/linkState.ts +1 -8
- package/src/menu/slip.ts +2 -2
- package/src/meter/meter.ts +25 -15
- package/src/notice/notice.ts +2 -1
- package/src/panel/panel.ts +5 -0
- package/src/passwordField/passwordField.ts +3 -7
- package/src/pixel-paper.css +200 -134
- package/src/rail/index.ts +2 -0
- package/src/rail/rail.ts +124 -44
- package/src/reel/layout.ts +14 -7
- package/src/reel/reel.ts +132 -63
- package/src/scheme/scheme.ts +1 -1
- package/src/segmented/segmented.ts +1 -9
- package/src/selectField/selectField.ts +3 -10
- package/src/sortable/index.ts +0 -1
- package/src/sortable/sortable.ts +35 -54
- package/src/stack/stack.ts +1 -1
- package/src/tabs/tabs.ts +1 -1
- package/src/textareaField/textareaField.ts +3 -7
- package/src/time/time.ts +6 -7
- package/src/tips/index.ts +1 -1
- package/src/tips/tips.ts +71 -35
- package/src/toast/index.ts +1 -1
- package/src/toast/toast.ts +2 -8
- package/src/tokens.css +7 -0
- package/src/toolbar/toolbar.ts +0 -5
- package/dist/src/inlineInput/inline.d.ts +0 -6
- package/dist/src/inlineInput/inline.d.ts.map +0 -1
- package/dist/src/inlineInput/inline.js +0 -7
- package/src/inlineInput/inline.ts +0 -13
package/src/reel/reel.ts
CHANGED
|
@@ -1,4 +1,16 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
Array,
|
|
3
|
+
Effect,
|
|
4
|
+
Equal,
|
|
5
|
+
Match,
|
|
6
|
+
Number,
|
|
7
|
+
Option,
|
|
8
|
+
Order,
|
|
9
|
+
Queue,
|
|
10
|
+
Schema,
|
|
11
|
+
Stream,
|
|
12
|
+
String,
|
|
13
|
+
} from "effect";
|
|
2
14
|
import * as Command from "foldkit/command";
|
|
3
15
|
import type { Attribute, Html, HtmlBuilder } from "foldkit/html";
|
|
4
16
|
import { defineMessageUnion } from "foldkit/message";
|
|
@@ -42,6 +54,7 @@ import {
|
|
|
42
54
|
columnOfTarget,
|
|
43
55
|
fill,
|
|
44
56
|
firstPlace,
|
|
57
|
+
isChange,
|
|
45
58
|
isSplitterPresent,
|
|
46
59
|
isTargetPresent,
|
|
47
60
|
landingWords,
|
|
@@ -61,21 +74,22 @@ import {
|
|
|
61
74
|
|
|
62
75
|
// MODEL
|
|
63
76
|
|
|
77
|
+
/** How the keyboard carries what it holds: said on the grip and once it is picked up. */
|
|
78
|
+
const KEYBOARD_MOVES = "Arrow keys move it, space drops it, escape or tab puts it back.";
|
|
79
|
+
|
|
64
80
|
/** The words a reel says. `{pane}` is a pane's label, `{subject}` what moves (the held pane,
|
|
65
81
|
* or a column a key moves, by its first pane), `{where}` a place and `{column}` a column's
|
|
66
82
|
* number. */
|
|
67
83
|
const COPY = {
|
|
68
84
|
paneGrip: "Move {pane}",
|
|
69
|
-
gripHelp:
|
|
70
|
-
"Press space to pick it up. Arrow keys move it, space drops it, escape or tab puts it back.",
|
|
85
|
+
gripHelp: `Press space to pick it up. ${KEYBOARD_MOVES}`,
|
|
71
86
|
widthLabel: "Width of column {column}",
|
|
72
87
|
heightLabel: "Height of {pane}",
|
|
73
88
|
focused: "{pane}, {where}.",
|
|
74
89
|
moved: "{subject} moved to {where}.",
|
|
75
90
|
widened: "Column {column} takes {width}.",
|
|
76
91
|
pickedUp: "Picked up {subject}.",
|
|
77
|
-
pickedUpByKeyboard:
|
|
78
|
-
"Picked up {subject}. Arrow keys move it, space drops it, escape or tab puts it back.",
|
|
92
|
+
pickedUpByKeyboard: `Picked up {subject}. ${KEYBOARD_MOVES}`,
|
|
79
93
|
over: "{subject}, {where}.",
|
|
80
94
|
dropped: "{subject} dropped at {where}.",
|
|
81
95
|
putBack: "{subject} put back where it was.",
|
|
@@ -665,7 +679,7 @@ const requestStep = (
|
|
|
665
679
|
maybeRequest: Option.Option<OutMessage>,
|
|
666
680
|
): UpdateReturn =>
|
|
667
681
|
Option.match(
|
|
668
|
-
Option.filter(maybeRequest, (request) =>
|
|
682
|
+
Option.filter(maybeRequest, (request) => isChange(layout, request)),
|
|
669
683
|
{
|
|
670
684
|
onNone: () => ({ model }),
|
|
671
685
|
onSome: (request) => {
|
|
@@ -823,29 +837,32 @@ const EDGE_SCROLL = 40;
|
|
|
823
837
|
const EDGE_SCROLL_PIXELS_PER_MILLI = 0.8;
|
|
824
838
|
const LONGEST_FRAME_MILLIS = 50;
|
|
825
839
|
|
|
826
|
-
/** A column as the pointer reads it from the page: its edges
|
|
827
|
-
|
|
828
|
-
const columnBoxOf = (element: Element, heldId: string): ColumnBox => {
|
|
840
|
+
/** A column as the pointer reads it from the page: its edges. */
|
|
841
|
+
const columnBoxOf = (element: Element): ColumnBox => {
|
|
829
842
|
const rect = element.getBoundingClientRect();
|
|
830
843
|
return {
|
|
831
844
|
id: element.getAttribute("data-reel-column") ?? "",
|
|
832
845
|
left: rect.left,
|
|
833
846
|
right: rect.right,
|
|
834
|
-
slotMiddles: Array.map(
|
|
835
|
-
Array.filter(
|
|
836
|
-
Array.fromIterable(element.querySelectorAll("[data-reel-slot]")),
|
|
837
|
-
(slot) => slot.getAttribute("data-reel-slot") !== heldId,
|
|
838
|
-
),
|
|
839
|
-
(slot) => {
|
|
840
|
-
const slotRect = slot.getBoundingClientRect();
|
|
841
|
-
return slotRect.top + slotRect.height / 2;
|
|
842
|
-
},
|
|
843
|
-
),
|
|
844
847
|
};
|
|
845
848
|
};
|
|
846
849
|
|
|
850
|
+
/** The middles of a column's slots but the held pane's. */
|
|
851
|
+
const slotMiddlesOf = (element: Element, heldId: string): ReadonlyArray<number> =>
|
|
852
|
+
Array.map(
|
|
853
|
+
Array.filter(
|
|
854
|
+
Array.fromIterable(element.querySelectorAll("[data-reel-slot]")),
|
|
855
|
+
(slot) => slot.getAttribute("data-reel-slot") !== heldId,
|
|
856
|
+
),
|
|
857
|
+
(slot) => {
|
|
858
|
+
const slotRect = slot.getBoundingClientRect();
|
|
859
|
+
return slotRect.top + slotRect.height / 2;
|
|
860
|
+
},
|
|
861
|
+
);
|
|
862
|
+
|
|
847
863
|
/** Where a pointer at (`clientX`, `clientY`) would drop the pane `paneId` in the reel
|
|
848
|
-
* `reelHook` names, read from where its columns and
|
|
864
|
+
* `reelHook` names, read from where its columns sit and the slots of the one it is over: none
|
|
865
|
+
* outside the reel. */
|
|
849
866
|
const pointerTarget = (
|
|
850
867
|
reelHook: string,
|
|
851
868
|
paneId: string,
|
|
@@ -862,14 +879,15 @@ const pointerTarget = (
|
|
|
862
879
|
clientY <= rect.bottom
|
|
863
880
|
);
|
|
864
881
|
}),
|
|
865
|
-
(view) =>
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
)
|
|
882
|
+
(view) => {
|
|
883
|
+
const columns = Array.fromIterable(view.querySelectorAll("[data-reel-column]"));
|
|
884
|
+
return targetAtPoint(Array.map(columns, columnBoxOf), clientX, clientY, (index) =>
|
|
885
|
+
Option.match(Array.get(columns, index), {
|
|
886
|
+
onNone: () => [],
|
|
887
|
+
onSome: (column) => slotMiddlesOf(column, paneId),
|
|
888
|
+
}),
|
|
889
|
+
);
|
|
890
|
+
},
|
|
873
891
|
);
|
|
874
892
|
|
|
875
893
|
const PointerHold = Schema.Struct({
|
|
@@ -885,13 +903,60 @@ const pointerAt = (hold: PointerHold, event: PointerEvent): PointerAt => ({
|
|
|
885
903
|
maybeTarget: pointerTarget(hold.reelHook, hold.paneId, event.clientX, event.clientY),
|
|
886
904
|
});
|
|
887
905
|
|
|
888
|
-
/** The pressing pointer's moves, release and cancel; another pointer's are not the reel's.
|
|
906
|
+
/** The pressing pointer's moves, release and cancel; another pointer's are not the reel's. A
|
|
907
|
+
* pointer reports moves faster than the page draws, and each reads the reel's layout, so its
|
|
908
|
+
* moves are read once a frame, at the latest; a release first reads the move not read yet,
|
|
909
|
+
* so the pane is carried as far as the pointer went, and a cancel drops it. */
|
|
889
910
|
const pointerMessages = (hold: PointerHold): Stream.Stream<Message> =>
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
911
|
+
Stream.callback<Message>((queue) =>
|
|
912
|
+
Effect.acquireRelease(
|
|
913
|
+
Effect.sync(() => {
|
|
914
|
+
const state = { maybeMove: Option.none<PointerEvent>(), frame: 0 };
|
|
915
|
+
const offer = (message: Message): void => {
|
|
916
|
+
Queue.offerUnsafe(queue, message);
|
|
917
|
+
};
|
|
918
|
+
const readMove = (): void => {
|
|
919
|
+
cancelAnimationFrame(state.frame);
|
|
920
|
+
Option.map(state.maybeMove, (event) =>
|
|
921
|
+
offer(Message.MovedPointer(pointerAt(hold, event))),
|
|
922
|
+
);
|
|
923
|
+
state.maybeMove = Option.none();
|
|
924
|
+
};
|
|
925
|
+
const onMove = (event: PointerEvent): void => {
|
|
926
|
+
if (event.pointerId === hold.pointerId) {
|
|
927
|
+
if (Option.isNone(state.maybeMove)) {
|
|
928
|
+
state.frame = requestAnimationFrame(readMove);
|
|
929
|
+
}
|
|
930
|
+
state.maybeMove = Option.some(event);
|
|
931
|
+
}
|
|
932
|
+
};
|
|
933
|
+
const onUp = (event: PointerEvent): void => {
|
|
934
|
+
if (event.pointerId === hold.pointerId) {
|
|
935
|
+
readMove();
|
|
936
|
+
offer(Message.ReleasedPointer(pointerAt(hold, event)));
|
|
937
|
+
}
|
|
938
|
+
};
|
|
939
|
+
const onCancel = (event: PointerEvent): void => {
|
|
940
|
+
if (event.pointerId === hold.pointerId) {
|
|
941
|
+
cancelAnimationFrame(state.frame);
|
|
942
|
+
state.maybeMove = Option.none();
|
|
943
|
+
offer(Message.CancelledPointer());
|
|
944
|
+
}
|
|
945
|
+
};
|
|
946
|
+
document.addEventListener("pointermove", onMove);
|
|
947
|
+
document.addEventListener("pointerup", onUp);
|
|
948
|
+
document.addEventListener("pointercancel", onCancel);
|
|
949
|
+
return { state, onMove, onUp, onCancel };
|
|
950
|
+
}),
|
|
951
|
+
({ state, onMove, onUp, onCancel }) =>
|
|
952
|
+
Effect.sync(() => {
|
|
953
|
+
cancelAnimationFrame(state.frame);
|
|
954
|
+
document.removeEventListener("pointermove", onMove);
|
|
955
|
+
document.removeEventListener("pointerup", onUp);
|
|
956
|
+
document.removeEventListener("pointercancel", onCancel);
|
|
957
|
+
}),
|
|
958
|
+
),
|
|
959
|
+
);
|
|
895
960
|
|
|
896
961
|
/** Which way a pointer at `at` scrolls a box from `low` to `high`: towards the edge it is near. */
|
|
897
962
|
const towards = (at: number, low: number, high: number): number => {
|
|
@@ -1224,33 +1289,32 @@ const reelSplitterAttributes = (
|
|
|
1224
1289
|
h,
|
|
1225
1290
|
);
|
|
1226
1291
|
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1292
|
+
/** The pane a drop into `column` is marked on, and the class that marks it: the pane it would
|
|
1293
|
+
* land before, or the last one when it would land at the end, the carried pane left out. */
|
|
1294
|
+
const dropMarkOf = (
|
|
1295
|
+
info: ReelInfo,
|
|
1296
|
+
column: Column,
|
|
1297
|
+
): Option.Option<Readonly<{ paneId: string; markClass: string }>> =>
|
|
1298
|
+
Option.flatMap(info.maybeMark, (mark) => {
|
|
1299
|
+
if (mark._tag !== "Into" || mark.columnId !== column.id) {
|
|
1300
|
+
return Option.none();
|
|
1301
|
+
}
|
|
1302
|
+
const others = Array.filter(
|
|
1303
|
+
column.panes,
|
|
1304
|
+
(pane) => !Option.contains(info.maybeCarriedId, pane.id),
|
|
1305
|
+
);
|
|
1306
|
+
return Option.orElse(
|
|
1307
|
+
Option.map(Array.get(others, mark.index), ({ id }) => ({
|
|
1308
|
+
paneId: id,
|
|
1309
|
+
markClass: " drop-before",
|
|
1310
|
+
})),
|
|
1311
|
+
() =>
|
|
1238
1312
|
Option.map(
|
|
1239
|
-
Option.filter(Array.
|
|
1240
|
-
() => " drop-
|
|
1313
|
+
Option.filter(Array.last(others), () => mark.index >= others.length),
|
|
1314
|
+
({ id }) => ({ paneId: id, markClass: " drop-after" }),
|
|
1241
1315
|
),
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
Option.filter(
|
|
1245
|
-
Array.last(others),
|
|
1246
|
-
(pane) => mark.index >= others.length && pane.id === paneId,
|
|
1247
|
-
),
|
|
1248
|
-
() => " drop-after",
|
|
1249
|
-
),
|
|
1250
|
-
);
|
|
1251
|
-
}),
|
|
1252
|
-
{ onNone: () => "", onSome: (mark) => mark },
|
|
1253
|
-
);
|
|
1316
|
+
);
|
|
1317
|
+
});
|
|
1254
1318
|
|
|
1255
1319
|
const isGapMarked = (info: ReelInfo, maybeBeforeColumnId: Option.Option<string>): boolean =>
|
|
1256
1320
|
Option.exists(
|
|
@@ -1259,14 +1323,18 @@ const isGapMarked = (info: ReelInfo, maybeBeforeColumnId: Option.Option<string>)
|
|
|
1259
1323
|
);
|
|
1260
1324
|
|
|
1261
1325
|
const slotView = (
|
|
1262
|
-
info: ReelInfo,
|
|
1263
1326
|
anchors: ReadonlyMap<string, string>,
|
|
1264
|
-
|
|
1327
|
+
maybeDropMark: Option.Option<Readonly<{ paneId: string; markClass: string }>>,
|
|
1265
1328
|
pane: Pane,
|
|
1266
1329
|
h: HtmlBuilder<Message>,
|
|
1267
1330
|
): Html =>
|
|
1268
1331
|
h.keyed("div")(pane.id, [
|
|
1269
|
-
h.Class(
|
|
1332
|
+
h.Class(
|
|
1333
|
+
`reel-slot${Option.match(
|
|
1334
|
+
Option.filter(maybeDropMark, ({ paneId }) => paneId === pane.id),
|
|
1335
|
+
{ onNone: () => "", onSome: ({ markClass }) => markClass },
|
|
1336
|
+
)}`,
|
|
1337
|
+
),
|
|
1270
1338
|
h.DataAttribute("reel-slot", pane.id),
|
|
1271
1339
|
h.Style({ "--reel-weight": `${pane.weight}`, "--reel-anchor": anchors.get(pane.id) ?? "none" }),
|
|
1272
1340
|
]);
|
|
@@ -1293,13 +1361,14 @@ const columnView = (
|
|
|
1293
1361
|
): ReadonlyArray<Html> => {
|
|
1294
1362
|
const { model, layout } = info;
|
|
1295
1363
|
const total = Number.sumAll(Array.map(column.panes, (pane) => pane.weight));
|
|
1364
|
+
const maybeDropMark = dropMarkOf(info, column);
|
|
1296
1365
|
const stack = Array.flatMap(column.panes, (pane, row) => [
|
|
1297
1366
|
...Array.fromOption(
|
|
1298
1367
|
Option.map(Array.get(column.panes, row - 1), (above) =>
|
|
1299
1368
|
splitView(model, above, total > 0 ? above.weight / total : 0, h),
|
|
1300
1369
|
),
|
|
1301
1370
|
),
|
|
1302
|
-
slotView(
|
|
1371
|
+
slotView(anchors, maybeDropMark, pane, h),
|
|
1303
1372
|
]);
|
|
1304
1373
|
const maybeNextId = Option.map(Array.get(layout, index + 1), ({ id }) => id);
|
|
1305
1374
|
|
package/src/scheme/scheme.ts
CHANGED
|
@@ -21,7 +21,7 @@ export const toPinnedValue = (scheme: Scheme): Option.Option<string> =>
|
|
|
21
21
|
);
|
|
22
22
|
|
|
23
23
|
/** The scheme a `data-scheme` value pins. Anything else, or nothing, is `System`. */
|
|
24
|
-
export const fromPinnedValue = (value: string | null
|
|
24
|
+
export const fromPinnedValue = (value: string | null): Scheme =>
|
|
25
25
|
Match.value(value).pipe(
|
|
26
26
|
Match.withReturnType<Scheme>(),
|
|
27
27
|
Match.when("light", () => "Paper"),
|
|
@@ -2,7 +2,6 @@ import { Button as UiButton } from "@foldkit/ui";
|
|
|
2
2
|
import { Array } from "effect";
|
|
3
3
|
import type { Html, HtmlBuilder } from "foldkit/html";
|
|
4
4
|
|
|
5
|
-
import type { Attributes } from "../attributes/attributes.ts";
|
|
6
5
|
import { pressedAttribute } from "../button/button.ts";
|
|
7
6
|
import * as Glyph from "../glyph/glyph.ts";
|
|
8
7
|
import type { GlyphName } from "../glyph/glyphs.ts";
|
|
@@ -24,8 +23,6 @@ export type ViewConfig<Value extends string, Message> = Readonly<{
|
|
|
24
23
|
options: ReadonlyArray<Segment<Value>>;
|
|
25
24
|
value: Value;
|
|
26
25
|
onSelect: (value: Value) => Message;
|
|
27
|
-
/** Further attributes for the group, never a class. */
|
|
28
|
-
attributes?: Attributes<Message>;
|
|
29
26
|
}>;
|
|
30
27
|
|
|
31
28
|
const segmentView = <Value extends string, Message>(
|
|
@@ -62,12 +59,7 @@ export const view = <Value extends string, Message>(
|
|
|
62
59
|
const isGlyphs = Array.every(config.options, (option) => option.glyph !== undefined);
|
|
63
60
|
|
|
64
61
|
return h.div(
|
|
65
|
-
[
|
|
66
|
-
...(config.attributes ?? []),
|
|
67
|
-
h.Role("group"),
|
|
68
|
-
h.AriaLabel(config.label),
|
|
69
|
-
h.Class(isGlyphs ? "switch" : "nav"),
|
|
70
|
-
],
|
|
62
|
+
[h.Role("group"), h.AriaLabel(config.label), h.Class(isGlyphs ? "switch" : "nav")],
|
|
71
63
|
Array.map(config.options, (option) => segmentView(config, option, h)),
|
|
72
64
|
);
|
|
73
65
|
};
|
|
@@ -2,7 +2,7 @@ import { Select as UiSelect } from "@foldkit/ui";
|
|
|
2
2
|
import { Array } from "effect";
|
|
3
3
|
import type { Html, HtmlBuilder } from "foldkit/html";
|
|
4
4
|
|
|
5
|
-
import {
|
|
5
|
+
import { controlAttributes, errorView, helpView } from "../field/field.ts";
|
|
6
6
|
|
|
7
7
|
// VIEW
|
|
8
8
|
|
|
@@ -78,19 +78,12 @@ export const view = <Message>(config: ViewConfig<Message>, h: HtmlBuilder<Messag
|
|
|
78
78
|
[h.Class("field")],
|
|
79
79
|
[
|
|
80
80
|
h.label(groups.label, [label]),
|
|
81
|
-
// NOTE: @foldkit/ui gap: Select has no required option, so the wrapper says it.
|
|
82
81
|
h.select(
|
|
83
|
-
[
|
|
84
|
-
...groups.select,
|
|
85
|
-
...describedByError(id, error, help !== undefined, h),
|
|
86
|
-
...(isRequired ? [h.AriaRequired(true)] : []),
|
|
87
|
-
],
|
|
82
|
+
[...groups.select, ...controlAttributes(id, error, help, isRequired, h)],
|
|
88
83
|
Array.map(options, entryView(h)),
|
|
89
84
|
),
|
|
90
85
|
...errorView(id, error, h),
|
|
91
|
-
...(help
|
|
92
|
-
? []
|
|
93
|
-
: [h.p([...groups.description, h.Class("field-help")], [help])]),
|
|
86
|
+
...helpView(help, groups.description, h),
|
|
94
87
|
],
|
|
95
88
|
),
|
|
96
89
|
},
|
package/src/sortable/index.ts
CHANGED
package/src/sortable/sortable.ts
CHANGED
|
@@ -14,8 +14,9 @@ import * as Button from "../button/button.ts";
|
|
|
14
14
|
import {
|
|
15
15
|
LEFT_BUTTON,
|
|
16
16
|
activatedKeyboardDrag,
|
|
17
|
+
canLift,
|
|
17
18
|
cancelledDrag,
|
|
18
|
-
|
|
19
|
+
escapeSubscriptions,
|
|
19
20
|
insertionIndex,
|
|
20
21
|
isCarrying,
|
|
21
22
|
markDragging,
|
|
@@ -25,6 +26,7 @@ import {
|
|
|
25
26
|
pointerMessages as dragMessages,
|
|
26
27
|
pressedDraggable,
|
|
27
28
|
releasedPointer,
|
|
29
|
+
settle as settleDrag,
|
|
28
30
|
} from "../drag/drag.ts";
|
|
29
31
|
import * as Glyph from "../glyph/glyph.ts";
|
|
30
32
|
import * as IconButton from "../iconButton/iconButton.ts";
|
|
@@ -38,15 +40,16 @@ import { prefixKeys } from "../subscription/prefixKeys.ts";
|
|
|
38
40
|
export const Item = Schema.Struct({ id: Schema.String, label: Schema.String });
|
|
39
41
|
export type Item = typeof Item.Type;
|
|
40
42
|
|
|
43
|
+
/** How the keyboard carries what it holds: said on the grip and once it is picked up. */
|
|
44
|
+
const KEYBOARD_MOVES = "Arrow keys move it one place, space drops it, escape cancels.";
|
|
45
|
+
|
|
41
46
|
/** The words a sortable says. `{item}` stands for the item's label, `{position}` and `{total}`
|
|
42
47
|
* for where it is, counted from one. */
|
|
43
48
|
const COPY = {
|
|
44
49
|
gripLabel: "Move {item}",
|
|
45
|
-
gripHelp:
|
|
46
|
-
"Press space to pick the item up. Arrow keys move it one place, space drops it, escape cancels.",
|
|
50
|
+
gripHelp: `Press space to pick the item up. ${KEYBOARD_MOVES}`,
|
|
47
51
|
pickedUp: "Picked up {item}.",
|
|
48
|
-
pickedUpByKeyboard:
|
|
49
|
-
"Picked up {item}, {position} of {total}. Arrow keys move it one place, space drops it, escape cancels.",
|
|
52
|
+
pickedUpByKeyboard: `Picked up {item}, {position} of {total}. ${KEYBOARD_MOVES}`,
|
|
50
53
|
over: "{item}, {position} of {total}.",
|
|
51
54
|
dropped: "{item} dropped at {position} of {total}.",
|
|
52
55
|
putBack: "{item} put back where it was.",
|
|
@@ -150,10 +153,6 @@ export const init = (config: InitConfig): Model => ({
|
|
|
150
153
|
/** The list's id, unique in the document. */
|
|
151
154
|
const id = (model: Model): string => model.dragAndDrop.id;
|
|
152
155
|
|
|
153
|
-
/** Whether an item is pressed or held. */
|
|
154
|
-
export const isHolding = (model: Model): boolean =>
|
|
155
|
-
Option.isSome(UiDragAndDrop.maybeDraggedItemId(model.dragAndDrop));
|
|
156
|
-
|
|
157
156
|
// DOM HOOKS
|
|
158
157
|
|
|
159
158
|
const itemHook = (model: Model, itemId: string): string => `${id(model)}/${itemId}`;
|
|
@@ -204,10 +203,7 @@ const placementOf = (model: Model, held: Held, rawIndex: number): Option.Option<
|
|
|
204
203
|
itemId: held.item.id,
|
|
205
204
|
fromIndex,
|
|
206
205
|
toIndex,
|
|
207
|
-
maybeAfterItemId: Option.map(
|
|
208
|
-
toIndex > 0 ? Array.get(others, toIndex - 1) : Option.none(),
|
|
209
|
-
(item) => item.id,
|
|
210
|
-
),
|
|
206
|
+
maybeAfterItemId: Option.map(Array.get(others, toIndex - 1), (item) => item.id),
|
|
211
207
|
maybeBeforeItemId: Option.map(Array.get(others, toIndex), (item) => item.id),
|
|
212
208
|
});
|
|
213
209
|
});
|
|
@@ -283,38 +279,23 @@ const moveText = (
|
|
|
283
279
|
/** Announces the step a DragAndDrop update took and forgets the held item once it is down. */
|
|
284
280
|
const settle =
|
|
285
281
|
(previous: Model, putBack = COPY.putBack) =>
|
|
286
|
-
(dragAndDropUpdate: UpdateReturn): UpdateReturn =>
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
282
|
+
(dragAndDropUpdate: UpdateReturn): UpdateReturn =>
|
|
283
|
+
settleDrag(
|
|
284
|
+
dragAndDropUpdate,
|
|
285
|
+
Option.map(
|
|
286
|
+
Option.flatMap(previous.maybeHeld, (held) =>
|
|
287
|
+
moveText(
|
|
288
|
+
held,
|
|
289
|
+
previous,
|
|
290
|
+
dragAndDropUpdate.model,
|
|
291
|
+
Option.fromNullishOr(dragAndDropUpdate.outMessage),
|
|
292
|
+
putBack,
|
|
293
|
+
),
|
|
294
|
+
),
|
|
295
|
+
(text) => AnnounceReorder({ text }),
|
|
298
296
|
),
|
|
299
297
|
);
|
|
300
298
|
|
|
301
|
-
return Update.withOutMessage(
|
|
302
|
-
{
|
|
303
|
-
model: nextModel,
|
|
304
|
-
commands: [
|
|
305
|
-
...(dragAndDropUpdate.commands ?? []),
|
|
306
|
-
...Array.fromOption(Option.map(maybeText, (text) => AnnounceReorder({ text }))),
|
|
307
|
-
],
|
|
308
|
-
},
|
|
309
|
-
dragAndDropUpdate.outMessage,
|
|
310
|
-
);
|
|
311
|
-
};
|
|
312
|
-
|
|
313
|
-
// NOTE: @foldkit/ui gap: DragAndDrop's pointer resolves a drop along one axis, which a
|
|
314
|
-
// wrapping grid cannot use, so the list reads the pointer itself and hands DragAndDrop its
|
|
315
|
-
// moves and release.
|
|
316
|
-
const canLift = (model: Model): boolean => model.isMovable && phaseOf(model.dragAndDrop) === "Idle";
|
|
317
|
-
|
|
318
299
|
/** The item `itemId` and its place, when the list may lift it now. */
|
|
319
300
|
const liftable = (
|
|
320
301
|
model: Model,
|
|
@@ -322,8 +303,9 @@ const liftable = (
|
|
|
322
303
|
itemId: string,
|
|
323
304
|
): Option.Option<Readonly<{ index: number; item: Item }>> =>
|
|
324
305
|
Option.filter(
|
|
325
|
-
Option.
|
|
326
|
-
|
|
306
|
+
Option.map(
|
|
307
|
+
Array.findFirstWithIndex(items, (item) => item.id === itemId),
|
|
308
|
+
([item, index]) => ({ index, item }),
|
|
327
309
|
),
|
|
328
310
|
() => canLift(model),
|
|
329
311
|
);
|
|
@@ -441,6 +423,9 @@ export const setMovable = (model: Model, isMovable: boolean): UpdateReturn => {
|
|
|
441
423
|
|
|
442
424
|
// SUBSCRIPTION
|
|
443
425
|
|
|
426
|
+
// NOTE: @foldkit/ui gap: DragAndDrop's pointer resolves a drop along one axis, which a
|
|
427
|
+
// wrapping grid cannot use, so the list reads the pointer itself and hands DragAndDrop its
|
|
428
|
+
// moves and release.
|
|
444
429
|
/** Where a pointer at this point would drop the held item in the list `listId`: none when the
|
|
445
430
|
* pointer is not over it. */
|
|
446
431
|
const pointerIndex = (
|
|
@@ -480,7 +465,7 @@ const pointerMessages = (hold: PointerHold): Stream.Stream<Message> =>
|
|
|
480
465
|
cancelled: () => Message.CancelledPointer(),
|
|
481
466
|
});
|
|
482
467
|
|
|
483
|
-
const
|
|
468
|
+
const pointerSubscriptions = Subscription.make<Model, Message>()((entry) => ({
|
|
484
469
|
pointer: entry(
|
|
485
470
|
{ maybeHold: Schema.Option(PointerHold) },
|
|
486
471
|
{
|
|
@@ -506,14 +491,6 @@ const ownSubscriptions = Subscription.make<Model, Message>()((entry) => ({
|
|
|
506
491
|
}),
|
|
507
492
|
},
|
|
508
493
|
),
|
|
509
|
-
escape: entry(
|
|
510
|
-
{ isHolding: Schema.Boolean },
|
|
511
|
-
{
|
|
512
|
-
modelToDependencies: (model) => ({ isHolding: isHolding(model) }),
|
|
513
|
-
dependenciesToStream: ({ isHolding: isHoldingNow }) =>
|
|
514
|
-
isHoldingNow ? escapeKeys(Message.PressedEscape()) : Stream.empty,
|
|
515
|
-
},
|
|
516
|
-
),
|
|
517
494
|
}));
|
|
518
495
|
|
|
519
496
|
// NOTE: @foldkit/ui gap: DragAndDrop's Subscription keys are fixed, so two of its users on a
|
|
@@ -527,7 +504,11 @@ const dragSubscriptions = Subscription.lift({
|
|
|
527
504
|
toParentMessage: toDragAndDropMessage,
|
|
528
505
|
});
|
|
529
506
|
|
|
530
|
-
const sortableSubscriptions = Subscription.aggregate(
|
|
507
|
+
const sortableSubscriptions = Subscription.aggregate(
|
|
508
|
+
pointerSubscriptions,
|
|
509
|
+
escapeSubscriptions<Model, Message>(Message.PressedEscape()),
|
|
510
|
+
dragSubscriptions,
|
|
511
|
+
);
|
|
531
512
|
|
|
532
513
|
/** A held item follows the pointer, the arrows and Space move and drop it, Escape puts it back
|
|
533
514
|
* and the page scrolls near its edges, from listeners that run only while an item is pressed
|
package/src/stack/stack.ts
CHANGED
|
@@ -4,7 +4,7 @@ import type { Html, HtmlBuilder } from "foldkit/html";
|
|
|
4
4
|
// SPACE
|
|
5
5
|
|
|
6
6
|
/** `S`: things in one line (8px). `M`: the parts of one body (14px). `L`: whole surfaces,
|
|
7
|
-
* panel to panel (24px).
|
|
7
|
+
* panel to panel (24px). */
|
|
8
8
|
export const Space = Schema.Literals(["S", "M", "L"]);
|
|
9
9
|
export type Space = typeof Space.Type;
|
|
10
10
|
|
package/src/tabs/tabs.ts
CHANGED
|
@@ -40,7 +40,7 @@ export type OutMessage = typeof OutMessage.Type;
|
|
|
40
40
|
* document; each tab's and panel's id derives from it. */
|
|
41
41
|
export type InitConfig = Readonly<{ id: string }>;
|
|
42
42
|
|
|
43
|
-
/** Creates a tab strip. Arrows select as they move
|
|
43
|
+
/** Creates a tab strip. Arrows select as they move. */
|
|
44
44
|
export const init = (config: InitConfig): Model => ({
|
|
45
45
|
tabs: UiTabs.init({ id: config.id, activationMode: "Automatic" }),
|
|
46
46
|
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Textarea as UiTextarea } from "@foldkit/ui";
|
|
2
2
|
import type { ChildAttribute, Html, HtmlBuilder, TextareaAttribute } from "foldkit/html";
|
|
3
3
|
|
|
4
|
-
import {
|
|
4
|
+
import { controlAttributes, errorView, helpView } from "../field/field.ts";
|
|
5
5
|
|
|
6
6
|
// VIEW
|
|
7
7
|
|
|
@@ -59,17 +59,13 @@ export const view = <Message>(config: ViewConfig<Message>, h: HtmlBuilder<Messag
|
|
|
59
59
|
[h.Class(`field${isMono ? " mono" : ""}`)],
|
|
60
60
|
[
|
|
61
61
|
h.label(groups.label, [label]),
|
|
62
|
-
// NOTE: @foldkit/ui gap: Textarea has no required option, so the wrapper says it.
|
|
63
62
|
h.textarea([
|
|
64
63
|
...textareaAttributes,
|
|
65
64
|
...groups.textarea,
|
|
66
|
-
...
|
|
67
|
-
...(isRequired ? [h.AriaRequired(true)] : []),
|
|
65
|
+
...controlAttributes(id, error, help, isRequired, h),
|
|
68
66
|
]),
|
|
69
67
|
...errorView(id, error, h),
|
|
70
|
-
...(help
|
|
71
|
-
? []
|
|
72
|
-
: [h.p([...groups.description, h.Class("field-help")], [help])]),
|
|
68
|
+
...helpView(help, groups.description, h),
|
|
73
69
|
],
|
|
74
70
|
),
|
|
75
71
|
},
|
package/src/time/time.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Clock, Duration,
|
|
1
|
+
import { Clock, Duration, Match, Option, Schedule, Schema, Stream } from "effect";
|
|
2
2
|
import type { Html, HtmlBuilder } from "foldkit/html";
|
|
3
3
|
import type { EntryWithoutKeepAlive } from "foldkit/subscription";
|
|
4
4
|
|
|
@@ -127,10 +127,9 @@ export const clockEntry = <Model, Message>(
|
|
|
127
127
|
dependenciesSchema: Schema.Struct({ isActive: Schema.Boolean }),
|
|
128
128
|
modelToDependencies: (model) => ({ isActive: config.isActive(model) }),
|
|
129
129
|
dependenciesToStream: ({ isActive }) =>
|
|
130
|
-
|
|
131
|
-
Stream.fromEffectSchedule(Clock.currentTimeMillis, Schedule.spaced(TICK)).pipe(
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
),
|
|
130
|
+
isActive
|
|
131
|
+
? Stream.fromEffectSchedule(Clock.currentTimeMillis, Schedule.spaced(TICK)).pipe(
|
|
132
|
+
Stream.map(config.toMessage),
|
|
133
|
+
)
|
|
134
|
+
: Stream.empty,
|
|
136
135
|
});
|
package/src/tips/index.ts
CHANGED