@gjermundgaraba-designs/pixel-paper 0.18.0 → 0.19.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.
@@ -0,0 +1,370 @@
1
+ import { brandViewResult as __foldkitBrandViewResult } from 'foldkit/brand'
2
+ // The reel's layout, apart from its Model: the panes, columns and requests the application
3
+ // holds and applies, where a held pane or column may land, and how places are said.
4
+ import { Array, Equal, Match, Number, Option, Predicate, Record, Schema, pipe } from "effect";
5
+ import { defineMessageUnion } from "foldkit/message";
6
+ import { defineTaggedUnion } from "foldkit/schema";
7
+ import { LOCALE } from "../locale/locale.js";
8
+ // LAYOUT SCHEMAS
9
+ /** Schema for one pane as the reel knows it: its id, unique in the reel and the application's
10
+ * to keep, its name in words (its grip, its splitter and what screen readers hear), and its
11
+ * share of its column's height, a weight against its neighbours'. */
12
+ export const Pane = Schema.Struct({
13
+ id: Schema.String,
14
+ label: Schema.String,
15
+ weight: Schema.Number,
16
+ });
17
+ /** Schema for one column: its id, unique among the reel's columns and the application's to
18
+ * keep, its width, a fraction of the reel's visible width (1 fills it), and its panes, top
19
+ * to bottom. A column `apply` makes for a pane pulled out takes the pane's id (`notes`, or
20
+ * `notes/2` where a column has it), so pane and column ids share one namespace. */
21
+ export const Column = Schema.Struct({
22
+ id: Schema.String,
23
+ width: Schema.Number,
24
+ panes: Schema.NonEmptyArray(Pane),
25
+ });
26
+ /** Schema for a reel's layout: its columns, left to right. The application's to keep. */
27
+ export const Layout = Schema.Array(Column);
28
+ /** What is held: a pane or a column, by its id. */
29
+ export const Subject = defineTaggedUnion({
30
+ Pane: { paneId: Schema.String },
31
+ Column: { columnId: Schema.String },
32
+ });
33
+ /** Where a held pane or column would land: into a column, at a place among its other panes,
34
+ * or in the gap before a column (none: after the last), as a column of its own. */
35
+ export const Target = defineTaggedUnion({
36
+ Into: { columnId: Schema.String, index: Schema.Number },
37
+ Gap: { maybeBeforeColumnId: Schema.Option(Schema.String) },
38
+ });
39
+ // REQUEST
40
+ /** Union of what a reel asks the application to do to its layout, which the application
41
+ * applies (`apply` does it) or not, by id, so a request still means what it meant after the
42
+ * layout changed under it. `MovedPane`: a pane into a column, at `toIndex` among that
43
+ * column's other panes. `PulledOutPane`: a pane into a column of its own, before a column
44
+ * (none: after the last). `MovedColumn`: a column to before another (none: last).
45
+ * `ResizedColumn`: a column's width. `ResizedPanes`: a column's panes' weights, by pane id. */
46
+ export const OutMessage = defineMessageUnion({
47
+ MovedPane: { paneId: Schema.String, toColumnId: Schema.String, toIndex: Schema.Number },
48
+ PulledOutPane: { paneId: Schema.String, maybeBeforeColumnId: Schema.Option(Schema.String) },
49
+ MovedColumn: { columnId: Schema.String, maybeBeforeColumnId: Schema.Option(Schema.String) },
50
+ ResizedColumn: { columnId: Schema.String, width: Schema.Number },
51
+ ResizedPanes: {
52
+ columnId: Schema.String,
53
+ weights: Schema.Record(Schema.String, Schema.Number),
54
+ },
55
+ });
56
+ /** A splitter: the one after a column, which sizes its width, or the one below a pane, which
57
+ * shares the height between it and the pane under it. */
58
+ export const Splitter = defineTaggedUnion({
59
+ Width: { columnId: Schema.String },
60
+ Split: { abovePaneId: Schema.String },
61
+ });
62
+ /** A resize: a column's width, or its panes' weights. */
63
+ export const Resize = Schema.Union([OutMessage.ResizedColumn, OutMessage.ResizedPanes]);
64
+ const placesOf = (layout) => __foldkitBrandViewResult((Array.flatMap(layout, (column, columnIndex) => __foldkitBrandViewResult((Array.map(column.panes, (pane, row) => (__foldkitBrandViewResult(({ column, columnIndex, row, pane }), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#anonymous~2")))), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#anonymous"))), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#placesOf");
65
+ export const placeOf = (layout, paneId) => __foldkitBrandViewResult((Array.findFirst(placesOf(layout), (place) => __foldkitBrandViewResult((place.pane.id === paneId), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#anonymous~3"))), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#placeOf");
66
+ export const placeAt = (layout, columnIndex, row) => __foldkitBrandViewResult((Option.flatMap(Array.get(layout, columnIndex), (column) => __foldkitBrandViewResult((Option.map(Array.get(column.panes, row), (pane) => (__foldkitBrandViewResult(({ column, columnIndex, row, pane }), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#anonymous~5")))), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#anonymous~4"))), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#placeAt");
67
+ export const firstPlace = (layout) => __foldkitBrandViewResult((placeAt(layout, 0, 0)), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#firstPlace");
68
+ export const columnIndexOf = (layout, columnId) => __foldkitBrandViewResult((Array.findFirstIndex(layout, (column) => __foldkitBrandViewResult((column.id === columnId), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#anonymous~6"))), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#columnIndexOf");
69
+ export const columnOf = (layout, columnId) => __foldkitBrandViewResult((Array.findFirst(layout, (column) => __foldkitBrandViewResult((column.id === columnId), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#anonymous~7"))), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#columnOf");
70
+ /** The id of the column after the one at `columnIndex`: none after the last. */
71
+ const nextColumnId = (layout, columnIndex) => __foldkitBrandViewResult((Option.map(Array.get(layout, columnIndex + 1), (column) => __foldkitBrandViewResult((column.id), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#anonymous~8"))), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#nextColumnId");
72
+ const meanWeight = (panes) => __foldkitBrandViewResult((Array.match(panes, {
73
+ onEmpty: () => __foldkitBrandViewResult((1), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#onEmpty"),
74
+ onNonEmpty: (some) => __foldkitBrandViewResult((Number.sumAll(Array.map(some, (pane) => __foldkitBrandViewResult((pane.weight), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#anonymous~9"))) / some.length), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#onNonEmpty"),
75
+ })), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#meanWeight");
76
+ const othersIn = (column, paneId) => __foldkitBrandViewResult((Array.filter(column.panes, (pane) => __foldkitBrandViewResult((pane.id !== paneId), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#anonymous~10"))), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#othersIn");
77
+ const insertAt = (items, index, item) => {
78
+ const [before, after] = Array.splitAt(items, Number.clamp(index, { minimum: 0, maximum: items.length }));
79
+ return __foldkitBrandViewResult((Array.appendAll(Array.append(before, item), after)), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#insertAt");
80
+ };
81
+ /** `layout` with the pane `paneId` taken out, and any column it leaves empty gone. */
82
+ const without = (layout, paneId) => __foldkitBrandViewResult((Array.flatMap(layout, (column) => {
83
+ const others = othersIn(column, paneId);
84
+ return __foldkitBrandViewResult((Array.isReadonlyArrayNonEmpty(others) ? [{ ...column, panes: others }] : []), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#anonymous~11");
85
+ })), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#without");
86
+ /** Where a column goes when it is put before `maybeBeforeColumnId`: none when that column is
87
+ * not in `layout`. */
88
+ const gapIndex = (layout, maybeBeforeColumnId) => __foldkitBrandViewResult((Option.match(maybeBeforeColumnId, {
89
+ onNone: () => __foldkitBrandViewResult((Option.some(layout.length)), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#onNone"),
90
+ onSome: (columnId) => __foldkitBrandViewResult((columnIndexOf(layout, columnId)), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#onSome"),
91
+ })), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#gapIndex");
92
+ /** A column id for a column made for `paneId`: the pane's own id, or the first `id/2`,
93
+ * `id/3`… no column in `layout` has. */
94
+ const freshColumnId = (layout, paneId, suffix = 1) => {
95
+ const candidate = suffix === 1 ? paneId : `${paneId}/${suffix}`;
96
+ return __foldkitBrandViewResult((Array.some(layout, (column) => __foldkitBrandViewResult((column.id === candidate), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#anonymous~12"))
97
+ ? freshColumnId(layout, paneId, suffix + 1)
98
+ : candidate), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#freshColumnId");
99
+ };
100
+ const movePane = (layout, paneId, toColumnId, toIndex) => __foldkitBrandViewResult((Option.match(Option.all({ from: placeOf(layout, paneId), to: columnOf(layout, toColumnId) }), {
101
+ onNone: () => __foldkitBrandViewResult((layout), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#onNone~2"),
102
+ onSome: ({ from, to }) => {
103
+ const others = othersIn(to, paneId);
104
+ const weight = to.id === from.column.id ? from.pane.weight : meanWeight(others);
105
+ const panes = insertAt(others, toIndex, { ...from.pane, weight });
106
+ // the target column keeps its place even when the pane was alone in it
107
+ return __foldkitBrandViewResult((Array.flatMap(layout, (column) => __foldkitBrandViewResult((column.id === toColumnId ? [{ ...column, panes }] : without([column], paneId)), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#anonymous~13"))), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#onSome~2");
108
+ },
109
+ })), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#movePane");
110
+ const pullOutPane = (layout, paneId, maybeBeforeColumnId) => __foldkitBrandViewResult((Option.match(placeOf(layout, paneId), {
111
+ onNone: () => __foldkitBrandViewResult((layout), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#onNone~3"),
112
+ onSome: (from) => {
113
+ const isAlone = from.column.panes.length === 1;
114
+ const isWhereItIs = Equal.equals(maybeBeforeColumnId, Option.some(from.column.id)) ||
115
+ Equal.equals(maybeBeforeColumnId, nextColumnId(layout, from.columnIndex));
116
+ const rest = without(layout, paneId);
117
+ if (isAlone && isWhereItIs) {
118
+ return __foldkitBrandViewResult((layout), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#onSome~3");
119
+ }
120
+ return __foldkitBrandViewResult((Option.match(gapIndex(rest, maybeBeforeColumnId), {
121
+ onNone: () => __foldkitBrandViewResult((layout), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#onNone~4"),
122
+ onSome: (gap) => __foldkitBrandViewResult((insertAt(rest, gap, {
123
+ id: freshColumnId(rest, paneId),
124
+ width: from.column.width,
125
+ panes: [{ ...from.pane, weight: 1 }],
126
+ })), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#onSome~4"),
127
+ })), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#onSome~3");
128
+ },
129
+ })), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#pullOutPane");
130
+ const moveColumn = (layout, columnId, maybeBeforeColumnId) => {
131
+ const rest = Array.filter(layout, (column) => __foldkitBrandViewResult((column.id !== columnId), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#anonymous~14"));
132
+ return __foldkitBrandViewResult((Option.match(Option.all({
133
+ column: columnOf(layout, columnId),
134
+ gap: gapIndex(rest, maybeBeforeColumnId),
135
+ }), {
136
+ onNone: () => __foldkitBrandViewResult((layout), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#onNone~5"),
137
+ onSome: ({ column, gap }) => __foldkitBrandViewResult((insertAt(rest, gap, column)), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#onSome~5"),
138
+ })), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#moveColumn");
139
+ };
140
+ const modifyColumn = (layout, columnId, f) => __foldkitBrandViewResult((Array.map(layout, (column) => (__foldkitBrandViewResult((column.id === columnId ? f(column) : column), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#anonymous~15")))), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#modifyColumn");
141
+ /**
142
+ * The layout once `request` is applied: what the reel asked, done the way every client does
143
+ * it. A pane that leaves its column takes the mean weight of the column it joins; a column
144
+ * left without panes goes; a pane pulled out keeps its old column's width, in a column named
145
+ * for it. A request naming a pane or column the layout no longer has changes nothing.
146
+ */
147
+ export const apply = (layout, request) => __foldkitBrandViewResult((OutMessage.match(request, {
148
+ MovedPane: ({ paneId, toColumnId, toIndex }) => __foldkitBrandViewResult((movePane(layout, paneId, toColumnId, toIndex)), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#MovedPane"),
149
+ PulledOutPane: ({ paneId, maybeBeforeColumnId }) => __foldkitBrandViewResult((pullOutPane(layout, paneId, maybeBeforeColumnId)), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#PulledOutPane"),
150
+ MovedColumn: ({ columnId, maybeBeforeColumnId }) => __foldkitBrandViewResult((moveColumn(layout, columnId, maybeBeforeColumnId)), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#MovedColumn"),
151
+ ResizedColumn: ({ columnId, width }) => __foldkitBrandViewResult((modifyColumn(layout, columnId, (column) => (__foldkitBrandViewResult(({ ...column, width }), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#anonymous~16")))), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#ResizedColumn"),
152
+ ResizedPanes: ({ columnId, weights }) => __foldkitBrandViewResult((modifyColumn(layout, columnId, (column) => (__foldkitBrandViewResult(({
153
+ ...column,
154
+ panes: Array.map(column.panes, (pane) => (__foldkitBrandViewResult(({
155
+ ...pane,
156
+ weight: Option.getOrElse(Record.get(weights, pane.id), () => __foldkitBrandViewResult((pane.weight), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#anonymous~19")),
157
+ }), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#anonymous~18"))),
158
+ }), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#anonymous~17")))), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#ResizedPanes"),
159
+ })), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#apply");
160
+ // WIDTHS
161
+ /** How close a width is to a preset to count as it. */
162
+ const PRESET_TOLERANCE = 0.01;
163
+ /** The widths a column cycles through, as fractions of the reel's visible width, with the
164
+ * words they are said in and the mark a column's head prints. They live here, not in the
165
+ * tokens, because they are words as much as sizes. */
166
+ const PRESETS = [
167
+ { width: 1 / 3, words: "a third of the width", mark: "1/3" },
168
+ { width: 1 / 2, words: "half the width", mark: "1/2" },
169
+ { width: 2 / 3, words: "two thirds of the width", mark: "2/3" },
170
+ { width: 1, words: "the full width", mark: "full" },
171
+ ];
172
+ const presetOf = (width) => __foldkitBrandViewResult((Array.findFirst(PRESETS, (preset) => __foldkitBrandViewResult((Math.abs(preset.width - width) < PRESET_TOLERANCE), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#anonymous~20"))), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#presetOf");
173
+ const percent = (width) => __foldkitBrandViewResult((Math.round(width * 100).toLocaleString(LOCALE)), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#percent");
174
+ export const widthWords = (width) => __foldkitBrandViewResult((Option.match(presetOf(width), {
175
+ onNone: () => __foldkitBrandViewResult((`${percent(width)}% of the width`), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#onNone~6"),
176
+ onSome: ({ words }) => __foldkitBrandViewResult((words), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#onSome~6"),
177
+ })), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#widthWords");
178
+ export const widthMark = (width) => __foldkitBrandViewResult((Option.match(presetOf(width), {
179
+ onNone: () => __foldkitBrandViewResult((`${percent(width)}%`), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#onNone~7"),
180
+ onSome: ({ mark }) => __foldkitBrandViewResult((mark), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#onSome~7"),
181
+ })), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#widthMark");
182
+ /** The next preset wider than `width`, or the narrowest past the widest. */
183
+ export const nextPreset = (width) => __foldkitBrandViewResult((Option.getOrElse(Array.findFirst(PRESETS, (preset) => __foldkitBrandViewResult((preset.width > width + PRESET_TOLERANCE), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#anonymous~21")), () => __foldkitBrandViewResult((Array.headNonEmpty(PRESETS)), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#anonymous~22")).width), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#nextPreset");
184
+ // WORDS
185
+ /** The words a place is said in. `{pane}` is a pane's label and `{column}` a column's
186
+ * number, counted from one. */
187
+ const WORDS = {
188
+ columnSubject: "column with {pane}",
189
+ place: "column {column} of {columns}",
190
+ stackedPlace: "{row} of {rows} in column {column} of {columns}",
191
+ };
192
+ /** What a place is called when a drop would leave the pane or column where it is. */
193
+ export const WHERE_IT_WAS = "where it was";
194
+ export const fill = (template, fields) => __foldkitBrandViewResult((Array.reduce(Object.entries(fields), template, (text, [key, value]) => __foldkitBrandViewResult((text.replaceAll(`{${key}}`, Predicate.isNumber(value) ? value.toLocaleString(LOCALE) : value)), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#anonymous~23"))), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#fill");
195
+ export const subjectWords = (layout, subject) => __foldkitBrandViewResult((Subject.match(subject, {
196
+ Pane: ({ paneId }) => __foldkitBrandViewResult((Option.map(placeOf(layout, paneId), ({ pane }) => __foldkitBrandViewResult((pane.label), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#anonymous~24"))), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#Pane"),
197
+ Column: ({ columnId }) => __foldkitBrandViewResult((Option.map(columnOf(layout, columnId), (column) => __foldkitBrandViewResult((fill(WORDS.columnSubject, { pane: Array.headNonEmpty(column.panes).label })), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#anonymous~25"))), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#Column"),
198
+ })), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#subjectWords");
199
+ export const placeWords = (layout, place) => {
200
+ const rows = place.column.panes.length;
201
+ const fields = {
202
+ column: place.columnIndex + 1,
203
+ columns: layout.length,
204
+ row: place.row + 1,
205
+ rows,
206
+ };
207
+ return __foldkitBrandViewResult((rows > 1 ? fill(WORDS.stackedPlace, fields) : fill(WORDS.place, fields)), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#placeWords");
208
+ };
209
+ /** Where `subject` sits in `layout`, in words. */
210
+ export const whereWords = (layout, subject) => __foldkitBrandViewResult((Subject.match(subject, {
211
+ Pane: ({ paneId }) => __foldkitBrandViewResult((Option.map(placeOf(layout, paneId), (place) => __foldkitBrandViewResult((placeWords(layout, place)), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#anonymous~26"))), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#Pane~2"),
212
+ Column: ({ columnId }) => __foldkitBrandViewResult((Option.map(columnIndexOf(layout, columnId), (column) => __foldkitBrandViewResult((fill(WORDS.place, { column: column + 1, columns: layout.length })), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#anonymous~27"))), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#Column~2"),
213
+ })), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#whereWords");
214
+ // TARGETS
215
+ /** Where `subject` is now, as a target: a pane at its place, a column in the gap before
216
+ * itself. */
217
+ export const originOf = (layout, subject) => __foldkitBrandViewResult((Subject.match(subject, {
218
+ Pane: ({ paneId }) => __foldkitBrandViewResult((Option.map(placeOf(layout, paneId), ({ column, row }) => __foldkitBrandViewResult((Target.Into({ columnId: column.id, index: row })), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#anonymous~28"))), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#Pane~3"),
219
+ Column: ({ columnId }) => __foldkitBrandViewResult((Option.map(columnIndexOf(layout, columnId), () => __foldkitBrandViewResult((Target.Gap({ maybeBeforeColumnId: Option.some(columnId) })), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#anonymous~29"))), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#Column~3"),
220
+ })), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#originOf");
221
+ export const isPresent = (layout, subject) => __foldkitBrandViewResult((Option.isSome(originOf(layout, subject))), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#isPresent");
222
+ /** What dropping `subject` at `target` asks of the layout: none where it would change
223
+ * nothing (where it is, or a target whose column is gone). */
224
+ export const requestOf = (layout, subject, target) => __foldkitBrandViewResult((Option.filter(Subject.match(subject, {
225
+ Pane: ({ paneId }) => __foldkitBrandViewResult((Option.some(Target.match(target, {
226
+ Into: ({ columnId, index }) => __foldkitBrandViewResult((OutMessage.MovedPane({ paneId, toColumnId: columnId, toIndex: index })), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#Into"),
227
+ Gap: ({ maybeBeforeColumnId }) => __foldkitBrandViewResult((OutMessage.PulledOutPane({ paneId, maybeBeforeColumnId })), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#Gap"),
228
+ }))), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#Pane~4"),
229
+ Column: ({ columnId }) => __foldkitBrandViewResult((Target.match(target, {
230
+ Into: () => __foldkitBrandViewResult((Option.none()), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#Into~2"),
231
+ Gap: ({ maybeBeforeColumnId }) => __foldkitBrandViewResult((Option.some(OutMessage.MovedColumn({ columnId, maybeBeforeColumnId }))), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#Gap~2"),
232
+ })), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#Column~4"),
233
+ }), (request) => __foldkitBrandViewResult((!Equal.equals(apply(layout, request), layout)), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#anonymous~30"))), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#requestOf");
234
+ /** Where `subject` would be once dropped at `target`, in words. */
235
+ export const landingWords = (layout, subject, target) => __foldkitBrandViewResult((Option.match(requestOf(layout, subject, target), {
236
+ onNone: () => __foldkitBrandViewResult((WHERE_IT_WAS), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#onNone~8"),
237
+ onSome: (request) => __foldkitBrandViewResult((Option.getOrElse(whereWords(apply(layout, request), subject), () => __foldkitBrandViewResult((WHERE_IT_WAS), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#anonymous~31"))), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#onSome~8"),
238
+ })), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#landingWords");
239
+ /** The column a target sits in or before (the last, for the gap after it), which a keyboard
240
+ * step scrolls to. */
241
+ export const columnOfTarget = (layout, target) => __foldkitBrandViewResult((Target.match(target, {
242
+ Into: ({ columnId }) => __foldkitBrandViewResult((Option.some(columnId)), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#Into~3"),
243
+ Gap: ({ maybeBeforeColumnId }) => __foldkitBrandViewResult((Option.orElse(maybeBeforeColumnId, () => __foldkitBrandViewResult((Option.map(Array.last(layout), ({ id }) => __foldkitBrandViewResult((id), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#anonymous~33"))), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#anonymous~32"))), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#Gap~3"),
244
+ })), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#columnOfTarget");
245
+ export const Step = Schema.Literals(["Up", "Down", "Left", "Right"]);
246
+ // A keyboard step reads the layout as a row of stops: the gap before each column (even), then
247
+ // the column itself (odd), and the gap after the last. A column steps from gap to gap.
248
+ const stopOf = (layout, target) => __foldkitBrandViewResult((Target.match(target, {
249
+ Into: ({ columnId }) => __foldkitBrandViewResult((Option.map(columnIndexOf(layout, columnId), (column) => __foldkitBrandViewResult((column * 2 + 1), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#anonymous~34"))), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#Into~4"),
250
+ Gap: ({ maybeBeforeColumnId }) => __foldkitBrandViewResult((Option.map(gapIndex(layout, maybeBeforeColumnId), (gap) => __foldkitBrandViewResult((gap * 2), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#anonymous~35"))), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#Gap~4"),
251
+ })), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#stopOf");
252
+ /** Whether the column `target` names (into it, or the gap before it) is still in `layout`. */
253
+ export const isTargetPresent = (layout, target) => __foldkitBrandViewResult((Option.isSome(stopOf(layout, target))), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#isTargetPresent");
254
+ const targetAtStop = (layout, subject, stop, row) => __foldkitBrandViewResult((stop % 2 === 0
255
+ ? Target.Gap({
256
+ maybeBeforeColumnId: Option.map(Array.get(layout, stop / 2), ({ id }) => __foldkitBrandViewResult((id), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#anonymous~36")),
257
+ })
258
+ : Option.match(Array.get(layout, (stop - 1) / 2), {
259
+ onNone: () => __foldkitBrandViewResult((Target.Gap({ maybeBeforeColumnId: Option.none() })), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#onNone~9"),
260
+ onSome: (column) => __foldkitBrandViewResult((Target.Into({ columnId: column.id, index: Math.min(row, roomIn(column, subject)) })), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#onSome~9"),
261
+ })), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#targetAtStop");
262
+ /** The last index `subject` may take among a column's panes. */
263
+ const roomIn = (column, subject) => __foldkitBrandViewResult((Subject.match(subject, {
264
+ Pane: ({ paneId }) => __foldkitBrandViewResult((othersIn(column, paneId).length), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#Pane~5"),
265
+ Column: () => __foldkitBrandViewResult((0), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#Column~5"),
266
+ })), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#roomIn");
267
+ const stepOnce = (layout, subject, target, step) => {
268
+ const row = Target.match(target, { Into: ({ index }) => __foldkitBrandViewResult((index), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#Into~5"), Gap: () => __foldkitBrandViewResult((0), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#Gap~5") });
269
+ const stride = subject._tag === "Column" ? 2 : 1;
270
+ return __foldkitBrandViewResult((Option.match(stopOf(layout, target), {
271
+ onNone: () => __foldkitBrandViewResult((target), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#onNone~10"),
272
+ onSome: (stop) => __foldkitBrandViewResult((Match.value(step).pipe(Match.withReturnType(), Match.when("Left", () => __foldkitBrandViewResult((targetAtStop(layout, subject, Math.max(stop - stride, 0), row)), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#anonymous~37")), Match.when("Right", () => __foldkitBrandViewResult((targetAtStop(layout, subject, Math.min(stop + stride, layout.length * 2), row)), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#anonymous~38")), Match.whenOr("Up", "Down", () => __foldkitBrandViewResult((Target.match(target, {
273
+ Gap: () => __foldkitBrandViewResult((target), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#Gap~6"),
274
+ Into: ({ columnId }) => __foldkitBrandViewResult((Option.match(columnOf(layout, columnId), {
275
+ onNone: () => __foldkitBrandViewResult((target), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#onNone~11"),
276
+ onSome: (column) => __foldkitBrandViewResult((Target.Into({
277
+ columnId,
278
+ index: Number.clamp(step === "Up" ? row - 1 : row + 1, {
279
+ minimum: 0,
280
+ maximum: roomIn(column, subject),
281
+ }),
282
+ })), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#onSome~11"),
283
+ })), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#Into~6"),
284
+ })), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#anonymous~39")), Match.exhaustive)), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#onSome~10"),
285
+ })), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#stepOnce");
286
+ };
287
+ /** A keyboard step from `target`: left and right cross from a column to the gap beside it and
288
+ * on to the next column, up and down move among a column's panes. A stop where the drop would
289
+ * change nothing is passed over, but for where `subject` started; with nothing further that
290
+ * way, it stays (where `subject` started, if the target's column has gone). */
291
+ export const steppedTarget = (layout, subject, target, step) => {
292
+ const origin = originOf(layout, subject);
293
+ // a target whose column has gone steps from where `subject` started instead
294
+ const start = isTargetPresent(layout, target) ? target : Option.getOrElse(origin, () => __foldkitBrandViewResult((target), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#anonymous~40"));
295
+ const isStop = (candidate) => __foldkitBrandViewResult((Option.isSome(requestOf(layout, subject, candidate)) ||
296
+ Option.exists(origin, (place) => __foldkitBrandViewResult((Equal.equals(place, candidate)), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#anonymous~41"))), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#isStop");
297
+ const walk = (from, left) => {
298
+ const next = stepOnce(layout, subject, from, step);
299
+ if (Equal.equals(next, from) || left === 0) {
300
+ return __foldkitBrandViewResult((start), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#walk");
301
+ }
302
+ return __foldkitBrandViewResult((isStop(next) ? next : walk(next, left - 1)), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#walk");
303
+ };
304
+ return __foldkitBrandViewResult((walk(start, layout.length * 2 + Array.length(placesOf(layout)))), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#steppedTarget");
305
+ };
306
+ /** The share of a column's width, from either edge, where a pointer over it means the gap
307
+ * beside it (a column of its own) rather than a place among its panes, up to `EDGE_MAX`
308
+ * pixels. */
309
+ const EDGE_SHARE = 0.2;
310
+ const EDGE_MAX = 48;
311
+ const gapBefore = (columns, gap) => __foldkitBrandViewResult((Target.Gap({ maybeBeforeColumnId: Option.map(Array.get(columns, gap), ({ id }) => __foldkitBrandViewResult((id), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#anonymous~42")) })), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#gapBefore");
312
+ /** Where a pointer at (`clientX`, `clientY`) over a reel whose columns sit at `columns`, left
313
+ * to right, would drop `subject`. A column lands in the gap nearest the pointer, counted by
314
+ * the columns whose middle it has passed. A pane over a column's edge band (or over a gap, or
315
+ * past the last column) lands in that gap, as a column of its own; over the rest of a column
316
+ * it lands among that column's other panes, after each whose middle is above the pointer. */
317
+ export const targetAtPoint = (subject, columns, clientX, clientY) => __foldkitBrandViewResult((Subject.match(subject, {
318
+ Column: () => __foldkitBrandViewResult((gapBefore(columns, Array.filter(columns, ({ left, right }) => __foldkitBrandViewResult(((left + right) / 2 < clientX), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#anonymous~43")).length)), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#Column~6"),
319
+ Pane: () => __foldkitBrandViewResult((pipe(Array.findFirstIndex(columns, ({ right }) => __foldkitBrandViewResult((clientX <= right), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#anonymous~44")), Option.flatMap((index) => __foldkitBrandViewResult((Option.map(Array.get(columns, index), (box) => (__foldkitBrandViewResult(({ index, box }), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#anonymous~46")))), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#anonymous~45")), Option.match({
320
+ onNone: () => __foldkitBrandViewResult((gapBefore(columns, columns.length)), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#onNone~12"),
321
+ onSome: ({ index, box: { id, left, right, slotMiddles } }) => {
322
+ const band = Math.min((right - left) * EDGE_SHARE, EDGE_MAX);
323
+ if (clientX < left + band) {
324
+ return __foldkitBrandViewResult((gapBefore(columns, index)), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#onSome~12");
325
+ }
326
+ else if (clientX > right - band) {
327
+ return __foldkitBrandViewResult((gapBefore(columns, index + 1)), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#onSome~12");
328
+ }
329
+ else {
330
+ return __foldkitBrandViewResult((Target.Into({
331
+ columnId: id,
332
+ index: Array.filter(slotMiddles, (middle) => __foldkitBrandViewResult((middle < clientY), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#anonymous~47")).length,
333
+ })), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#onSome~12");
334
+ }
335
+ },
336
+ }))), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#Pane~6"),
337
+ })), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#targetAtPoint");
338
+ // RESIZE
339
+ /** The narrowest a column's width goes by a splitter, and the least share of its column a pane
340
+ * keeps: the stylesheet keeps both on screen at their minimums, so these only keep a stored
341
+ * size from reaching nothing. */
342
+ const WIDTH_FLOOR = 0.1;
343
+ const SHARE_FLOOR = 0.05;
344
+ const PRECISION = 10_000;
345
+ const rounded = (value) => __foldkitBrandViewResult((Math.round(value * PRECISION) / PRECISION), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#rounded");
346
+ /** What moving `splitter` by `share` asks of the layout, from the sizes the layout holds (not
347
+ * the sizes printed, which the stylesheet may hold at a minimum): a width splitter adds
348
+ * `share` to its column's width; a pane splitter moves `share` of its column's height from
349
+ * the pane below it to the pane above. None when what it sizes is gone. */
350
+ export const resizeOf = (layout, splitter, share) => __foldkitBrandViewResult((Splitter.match(splitter, {
351
+ Width: ({ columnId }) => __foldkitBrandViewResult((Option.map(columnOf(layout, columnId), (column) => __foldkitBrandViewResult((OutMessage.ResizedColumn({
352
+ columnId,
353
+ width: rounded(Number.clamp(column.width + share, { minimum: WIDTH_FLOOR, maximum: 1 })),
354
+ })), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#anonymous~48"))), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#Width"),
355
+ Split: ({ abovePaneId }) => __foldkitBrandViewResult((Option.flatMap(placeOf(layout, abovePaneId), ({ column, row, pane: above }) => __foldkitBrandViewResult((Option.map(Array.get(column.panes, row + 1), (below) => {
356
+ const total = Number.sumAll(Array.map(column.panes, ({ weight }) => __foldkitBrandViewResult((weight), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#anonymous~51")));
357
+ const pair = above.weight + below.weight;
358
+ const floor = Math.min(total * SHARE_FLOOR, pair / 2);
359
+ const nextAbove = Number.clamp(above.weight + share * total, {
360
+ minimum: floor,
361
+ maximum: pair - floor,
362
+ });
363
+ return __foldkitBrandViewResult((OutMessage.ResizedPanes({
364
+ columnId: column.id,
365
+ weights: { [above.id]: rounded(nextAbove), [below.id]: rounded(pair - nextAbove) },
366
+ })), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#anonymous~50");
367
+ })), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#anonymous~49"))), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#Split"),
368
+ })), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#resizeOf");
369
+ /** Whether what `splitter` sizes is still in `layout`. */
370
+ export const isSplitterPresent = (layout, splitter) => __foldkitBrandViewResult((Option.isSome(resizeOf(layout, splitter, 0))), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#isSplitterPresent");