@gjermundgaraba-designs/pixel-paper 0.15.0 → 0.17.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 +2 -2
- 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/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 +22 -10
- package/dist/src/sortable/sortable.d.ts.map +1 -1
- package/dist/src/sortable/sortable.js +56 -72
- package/package.json +1 -1
- package/src/panel/panel.ts +5 -1
- package/src/pixel-paper.css +84 -19
- package/src/sortable/index.ts +1 -0
- package/src/sortable/sortable.ts +73 -78
package/DESIGN.md
CHANGED
|
@@ -1037,7 +1037,7 @@ The printed button: a paper rectangle in a 2px ink frame that casts a 3px shadow
|
|
|
1037
1037
|
|
|
1038
1038
|
### Panels
|
|
1039
1039
|
|
|
1040
|
-
- **Panel** → `section.panel[aria-labelledby] > header.panel-tb > h2 + span.meta`, then `div.panel-body` (or the body directly when `isFlush`), then `div.panel-foot`. The section is keyed by its `id`, so a panel among siblings that come and go keeps its own element, and a panel under another id is another element: one that replaces another in the same place is built anew (a live region inside it is inserted, not kept), so keep one panel under one id across states that should share it. Square, 2px ink border, Paper White, panel shadow. The title bar is inverted Departure Mono 13px uppercase 0.08em (`--tracking-title`), `padding: 6px 12px`; its meta is 11px at 75% opacity. A control in a title bar (a panel's, a window's or a card's) rings in paper, 2px inside its own edge, because the ink ring would vanish on the ink bar. The body pads 16px with a 14px grid gap; the foot pads `10px 12px` over a 1px rule, with a `.push` slot that goes right. `actions` puts small Buttons and IconButtons in `span.panel-acts` (6px apart) at the title bar's right end, after the meta; the bar trims to `3px 3px 3px 12px` at 31px or more. On the ink bar a button is a paper outline (a primary one fills paper), flat, nudged 1px when pressed, its ring 4px inside in its own colour; disabled, it prints at 60%.
|
|
1040
|
+
- **Panel** → `section.panel[aria-labelledby] > header.panel-tb > span.panel-lead + h2 + span.meta`, then `div.panel-body` (or the body directly when `isFlush`), then `div.panel-foot`. The section is keyed by its `id`, so a panel among siblings that come and go keeps its own element, and a panel under another id is another element: one that replaces another in the same place is built anew (a live region inside it is inserted, not kept), so keep one panel under one id across states that should share it. Square, 2px ink border, Paper White, panel shadow. The title bar is inverted Departure Mono 13px uppercase 0.08em (`--tracking-title`), `padding: 6px 12px`; its meta is 11px at 75% opacity. A control in a title bar (a panel's, a window's or a card's) rings in paper, 2px inside its own edge, because the ink ring would vanish on the ink bar. The body pads 16px with a 14px grid gap; the foot pads `10px 12px` over a 1px rule, with a `.push` slot that goes right. `actions` puts small Buttons and IconButtons in `span.panel-acts` (6px apart) at the title bar's right end, after the meta; the bar trims to `3px 3px 3px 12px` at 31px or more. `lead` puts a quiet IconButton in `span.panel-lead` before the title (a Sortable's grip), and the bar trims its left to 3px the same way; a quiet icon button there prints in paper, hovered too, and held at 60%. On the ink bar a button is a paper outline (a primary one fills paper), flat, nudged 1px when pressed, its ring 4px inside in its own colour; disabled, it prints at 60%.
|
|
1041
1041
|
- **Deck** → `main.deck#main`: the column of panels.
|
|
1042
1042
|
- **EmptyState** → `div.empty > h3.empty-title + p`: `headingLevel` is required and picks the title's element, which looks the same at every level. Where the empty state is a page's or a section's main content it is a heading, `3` inside a Panel (whose title is an `h2`), `1` for a gate or a missing page; `"Strong"` prints `strong.empty-title` where it is not (a chart with nothing to chart, a picture of the state). Centred, `26px 16px`, a 12px uppercase 0.08em (`--tracking-title`) headline, a muted 46ch sentence, an optional action 6px below.
|
|
1043
1043
|
- **Wall** → `main.wall`, `Wall.markView` → `div.wall-mark` (a 30px ink-bordered glyph box beside the 22px wordmark). `Wall.cornerView` → `aside.wall-corner[aria-label]`, rendered just before the Wall: a paper slip in a 2px ink box on the 3px shadow, `6px 8px`, holding what a top bar would (see Layout).
|
|
@@ -1097,7 +1097,7 @@ One keyboard for the page, Vim first, with the arrows beside the letters. The ke
|
|
|
1097
1097
|
|
|
1098
1098
|
### Sortable
|
|
1099
1099
|
|
|
1100
|
-
**Sortable** (Submodel over `@foldkit/ui`'s DragAndDrop, for items the reader puts in order: a list of rows or a grid of
|
|
1100
|
+
**Sortable** (Submodel over `@foldkit/ui`'s DragAndDrop, for items the reader puts in order: a list of rows, a grid of tiles, or a grid of the page's panels) → `div.sortable (.grid, .panels)` holding `ul.sortable-items[aria-label]` (a plain list: no listbox, so an item's content may hold anything) of `li.sortable-item (.movable, .drag-dim, .drop-before, .drop-after)`, each a grip, then `div.sortable-body` with the parent's content, and the list's keyboard help in a `p.sr-only`. `"List"` (the default) prints rows edge to edge between 1px rules, for a flush panel, `8px 16px 8px 10px`; `"Grid"` prints tiles that wrap, 180px and up, 12px apart: paper slips in a 2px ink border on the 3px print shadow. `"Panels"` prints each item as the parent's own Panel, up to three across in the deck's 24px gutter and 300px and up, so two across and then one as the column narrows; a row takes its tallest panel's height, each panel's foot sits at its bottom, and the list hands each item its grip (`render.grip`) for the panel's `lead` instead of printing it beside the item. The held copy is the whole panel, tinted head on the 8px card shadow, under ids of its own, and the drop line runs down the middle of the gutter. `isReadOnly`, for a reader who may look but not reorder, prints every grip held and lifts nothing, as holding moves does. Each item starts with a quiet `button.iconbtn` grip (in a panel's title bar, for `"Panels"`) (the grip glyph, named "Move <label>", `aria-roledescription="draggable"`, `aria-pressed`, described by the keyboard help); while moves are held it stays, held (`aria-disabled`, in the keyboard), so focus on it stays put when holding moves cancels one. Only the grip lifts an item, under a mouse, a finger or a pen (`cursor: grab` and `touch-action: none` on a movable grip only), so the rest of an item selects its text and a finger on it scrolls the page; the keyboard takes the grip too: Space or Enter picks the item up, every arrow moves it one place in reading order, Space or Enter drops it, Escape puts it back (taken before a dialog or the page's keys see it), and Tab leaves it where it is. Only the pointer that pressed moves the item; the browser cancelling it puts the item back. The held item dims to 40% in place (`.drag-dim`) and keeps its own print; a copy of it, `.lifted`, tinted head on the 8px card shadow in a 2px ink border (`aria-hidden`, `inert`, rendered with `isLifted`, so it carries no ids), follows the pointer in `div.sortable-lift`, or, held by the keyboard, prints in the list where it would land, in `li.sortable-slot`, counted as no item. Where a pointer would drop it, a 2px dashed ink line prints beside the item it lands next to (`.drop-before`, or `.drop-after` past the last), over the layout, so nothing moves under the pointer. The pointer's place is read from the item whose centre is closest, across where items sit side by side and down a column, and read again where the pointer is let go, so a page that scrolled under a still pointer drops where the pointer is. Each step is said through the page's live region in words from the item's `label` and its place, counted from one ("Beta, 3 of 4."). The items are the parent's: a drop reports `Moved`, and the parent moves the item, or decides not to, and flashes it; a drop where the item was, outside the list, or while moves are held reports nothing. While a pointer presses or carries an item the page's root carries `data-pixel-paper-dragging`, which prints the grabbing cursor everywhere and holds text selection.
|
|
1101
1101
|
|
|
1102
1102
|
### Inputs / Fields
|
|
1103
1103
|
|
|
@@ -9,6 +9,9 @@ export type ViewConfig = Readonly<{
|
|
|
9
9
|
id: string;
|
|
10
10
|
/** Pixel caps in the inverted title bar. */
|
|
11
11
|
title: string;
|
|
12
|
+
/** A quiet IconButton before the title, such as the grip a Sortable of panels hands each
|
|
13
|
+
* panel. On the ink bar it prints as `actions` do. */
|
|
14
|
+
lead?: ReadonlyArray<Html | string>;
|
|
12
15
|
/** A count or state at the title bar's right. */
|
|
13
16
|
meta?: string;
|
|
14
17
|
/** Small Buttons and IconButtons at the title bar's right end, after the meta. On the ink
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"panel.d.ts","sourceRoot":"","sources":["../../../src/panel/panel.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAMtD,uDAAuD;AACvD,MAAM,MAAM,UAAU,GAAG,QAAQ,CAAC;IAChC;;;;kCAI8B;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,4CAA4C;IAC5C,KAAK,EAAE,MAAM,CAAC;IACd,iDAAiD;IACjD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;yFACqF;IACrF,OAAO,CAAC,EAAE,aAAa,CAAC,IAAI,GAAG,MAAM,CAAC,CAAC;IACvC,IAAI,EAAE,aAAa,CAAC,IAAI,GAAG,MAAM,CAAC,CAAC;IACnC,2EAA2E;IAC3E,IAAI,CAAC,EAAE,aAAa,CAAC,IAAI,GAAG,MAAM,CAAC,CAAC;IACpC,uFAAuF;IACvF,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB,CAAC,CAAC;AAEH,iDAAiD;AACjD,eAAO,MAAM,OAAO,OAAQ,MAAM,KAAG,MAAuB,CAAC;AAE7D;gFACgF;AAChF,eAAO,MAAM,IAAI,GAAI,OAAO,UAAU,UAAU,KAAK,WAAW,CAAC,OAAO,CAAC,KAAG,
|
|
1
|
+
{"version":3,"file":"panel.d.ts","sourceRoot":"","sources":["../../../src/panel/panel.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAMtD,uDAAuD;AACvD,MAAM,MAAM,UAAU,GAAG,QAAQ,CAAC;IAChC;;;;kCAI8B;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,4CAA4C;IAC5C,KAAK,EAAE,MAAM,CAAC;IACd;2DACuD;IACvD,IAAI,CAAC,EAAE,aAAa,CAAC,IAAI,GAAG,MAAM,CAAC,CAAC;IACpC,iDAAiD;IACjD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;yFACqF;IACrF,OAAO,CAAC,EAAE,aAAa,CAAC,IAAI,GAAG,MAAM,CAAC,CAAC;IACvC,IAAI,EAAE,aAAa,CAAC,IAAI,GAAG,MAAM,CAAC,CAAC;IACnC,2EAA2E;IAC3E,IAAI,CAAC,EAAE,aAAa,CAAC,IAAI,GAAG,MAAM,CAAC,CAAC;IACpC,uFAAuF;IACvF,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB,CAAC,CAAC;AAEH,iDAAiD;AACjD,eAAO,MAAM,OAAO,OAAQ,MAAM,KAAG,MAAuB,CAAC;AAE7D;gFACgF;AAChF,eAAO,MAAM,IAAI,GAAI,OAAO,UAAU,UAAU,KAAK,WAAW,CAAC,OAAO,CAAC,KAAG,IAoB3E,CAAC"}
|
package/dist/src/panel/panel.js
CHANGED
|
@@ -5,9 +5,10 @@ export const titleId = (id) => __foldkitBrandViewResult((`${id}-title`), "@gjerm
|
|
|
5
5
|
/** The unit of the system: paper in a 2px ink box on a 5px print shadow, under a title bar. A
|
|
6
6
|
* region landmark (`<section>`) named by its `h2` title, keyed by its `id`. */
|
|
7
7
|
export const view = (config, h) => {
|
|
8
|
-
const { id, title, meta, actions = [], body, foot = [], isFlush = false } = config;
|
|
8
|
+
const { id, title, lead = [], meta, actions = [], body, foot = [], isFlush = false } = config;
|
|
9
9
|
return __foldkitBrandViewResult((h.keyed("section")(id, [h.Id(id), h.AriaLabelledBy(titleId(id)), h.Class("panel")], [
|
|
10
10
|
h.header([h.Class("panel-tb")], [
|
|
11
|
+
...slotView("span", "panel-lead", lead, h),
|
|
11
12
|
h.h2([h.Id(titleId(id))], [title]),
|
|
12
13
|
...(meta === undefined ? [] : [h.span([h.Class("meta")], [meta])]),
|
|
13
14
|
...slotView("span", "panel-acts", actions, h),
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export { AnnounceReorder, Item, Message, Model, OutMessage, SizeLiftedItem, init, setMovable, subscriptions, update, view, } from "./sortable.ts";
|
|
1
|
+
export { AnnounceReorder, Item, Message, Model, OutMessage, SizeLiftedItem, init, isHolding, setMovable, subscriptions, update, view, } from "./sortable.ts";
|
|
2
2
|
export type { InitConfig, ItemRender, Layout, ViewInputs } from "./sortable.ts";
|
|
3
3
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/sortable/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,eAAe,EACf,IAAI,EACJ,OAAO,EACP,KAAK,EACL,UAAU,EACV,cAAc,EACd,IAAI,EACJ,UAAU,EACV,aAAa,EACb,MAAM,EACN,IAAI,GACL,MAAM,eAAe,CAAC;AACvB,YAAY,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/sortable/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,eAAe,EACf,IAAI,EACJ,OAAO,EACP,KAAK,EACL,UAAU,EACV,cAAc,EACd,IAAI,EACJ,SAAS,EACT,UAAU,EACV,aAAa,EACb,MAAM,EACN,IAAI,GACL,MAAM,eAAe,CAAC;AACvB,YAAY,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export { AnnounceReorder, Item, Message, Model, OutMessage, SizeLiftedItem, init, setMovable, subscriptions, update, view, } from "./sortable.js";
|
|
1
|
+
export { AnnounceReorder, Item, Message, Model, OutMessage, SizeLiftedItem, init, isHolding, setMovable, subscriptions, update, view, } from "./sortable.js";
|
|
@@ -954,13 +954,16 @@ export declare const SizeLiftedItem: Mount.MountDefinitionWithArgs<"SizeLiftedIt
|
|
|
954
954
|
}, {
|
|
955
955
|
readonly _tag: "CompletedSizeLiftedItem";
|
|
956
956
|
}>;
|
|
957
|
-
/** How the items lay out: a column of rows,
|
|
958
|
-
export type Layout = "List" | "Grid";
|
|
957
|
+
/** How the items lay out: a column of rows, tiles that wrap, or the parent's own panels. */
|
|
958
|
+
export type Layout = "List" | "Grid" | "Panels";
|
|
959
959
|
/** What the list hands `toItemView` for each item. */
|
|
960
960
|
export type ItemRender = Readonly<{
|
|
961
961
|
/** This render is the held copy: hidden from screen readers and inert. Leave out ids and
|
|
962
962
|
* anything the page must find once. */
|
|
963
963
|
isLifted: boolean;
|
|
964
|
+
/** The item's grip, for the `"Panels"` layout, where the item places it: in its panel's
|
|
965
|
+
* `lead`. The other layouts print it before the item themselves. */
|
|
966
|
+
grip: Html;
|
|
964
967
|
}>;
|
|
965
968
|
/** Per-render inputs passed to `view` through `h.submodel`'s `viewInputs`. The `to*` members
|
|
966
969
|
* are functions because they may carry the parent's handlers. */
|
|
@@ -969,20 +972,25 @@ export type ViewInputs = Readonly<{
|
|
|
969
972
|
/** Names the list for screen readers. */
|
|
970
973
|
label: string;
|
|
971
974
|
/** `"List"` (the default): rows between 1px rules, for a flush panel. `"Grid"`: tiles that
|
|
972
|
-
* wrap, 180px and up.
|
|
975
|
+
* wrap, 180px and up. `"Panels"`: each item is a Panel the parent prints, with `render.grip`
|
|
976
|
+
* in its `lead`, up to three across in the deck's gutter, 300px and up. */
|
|
973
977
|
layout?: Layout;
|
|
978
|
+
/** The reader may look but not reorder, as a read-only sign-in: every grip prints held, as
|
|
979
|
+
* while moves are held, and nothing lifts. */
|
|
980
|
+
isReadOnly?: boolean;
|
|
974
981
|
/** One item's content, beside its grip. Also called for the held copy, with
|
|
975
982
|
* `render.isLifted`. */
|
|
976
983
|
toItemView: (item: Item, render: ItemRender) => ReadonlyArray<Html>;
|
|
977
984
|
}>;
|
|
978
985
|
/**
|
|
979
|
-
* Items the reader puts in order, as rows or as
|
|
980
|
-
* `div.sortable` holding `ul.sortable-items` (named by `label`) of
|
|
981
|
-
* grip and the parent's content
|
|
986
|
+
* Items the reader puts in order, as rows, as tiles or as panels, built on `@foldkit/ui`'s
|
|
987
|
+
* DragAndDrop: `div.sortable` holding `ul.sortable-items` (named by `label`) of
|
|
988
|
+
* `li.sortable-item`, each its grip and the parent's content (a panel holds its grip in its
|
|
989
|
+
* title bar). The items are the parent's; a drop reports `Moved`.
|
|
982
990
|
*
|
|
983
|
-
*
|
|
984
|
-
*
|
|
985
|
-
* up, every arrow moves it one place in reading order, Space drops it and Escape puts it back.
|
|
991
|
+
* Only the grip lifts an item, under every pointer, so the item's own text selects and a
|
|
992
|
+
* finger on it scrolls the page; the keyboard takes the grip too: Space or Enter picks the
|
|
993
|
+
* item up, every arrow moves it one place in reading order, Space drops it and Escape puts it back.
|
|
986
994
|
* The held item dims in place; a copy, lifted onto the card shadow, follows the pointer or
|
|
987
995
|
* prints where the keyboard would drop it; each step is said through the page's live region.
|
|
988
996
|
* Lift `subscriptions(keyPrefix)` into the application's, or nothing follows the pointer or
|
|
@@ -1134,8 +1142,12 @@ export declare const view: import("foldkit/submodel").View<{
|
|
|
1134
1142
|
/** Names the list for screen readers. */
|
|
1135
1143
|
label: string;
|
|
1136
1144
|
/** `"List"` (the default): rows between 1px rules, for a flush panel. `"Grid"`: tiles that
|
|
1137
|
-
* wrap, 180px and up.
|
|
1145
|
+
* wrap, 180px and up. `"Panels"`: each item is a Panel the parent prints, with `render.grip`
|
|
1146
|
+
* in its `lead`, up to three across in the deck's gutter, 300px and up. */
|
|
1138
1147
|
layout?: Layout;
|
|
1148
|
+
/** The reader may look but not reorder, as a read-only sign-in: every grip prints held, as
|
|
1149
|
+
* while moves are held, and nothing lifts. */
|
|
1150
|
+
isReadOnly?: boolean;
|
|
1139
1151
|
/** One item's content, beside its grip. Also called for the held copy, with
|
|
1140
1152
|
* `render.isLifted`. */
|
|
1141
1153
|
toItemView: (item: Item, render: ItemRender) => ReadonlyArray<Html>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sortable.d.ts","sourceRoot":"","sources":["../../../src/sortable/sortable.ts"],"names":[],"mappings":"AACA,OAAO,EAAS,MAAM,EAAS,MAAM,EAAE,MAAM,EAAE,MAAM,EAAQ,MAAM,QAAQ,CAAC;AAC5E,OAAO,KAAK,OAAO,MAAM,iBAAiB,CAAC;AAC3C,OAAO,KAAK,EAAa,IAAI,EAAe,MAAM,cAAc,CAAC;AAEjE,OAAO,KAAK,KAAK,MAAM,eAAe,CAAC;AAGvC,OAAO,KAAK,YAAY,MAAM,sBAAsB,CAAC;AACrD,OAAO,KAAK,MAAM,MAAM,gBAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"sortable.d.ts","sourceRoot":"","sources":["../../../src/sortable/sortable.ts"],"names":[],"mappings":"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"}
|
|
@@ -10,7 +10,7 @@ import * as Subscription from "foldkit/subscription";
|
|
|
10
10
|
import * as Update from "foldkit/update";
|
|
11
11
|
import * as Announcer from "../announcer/announcer.js";
|
|
12
12
|
import * as Button from "../button/button.js";
|
|
13
|
-
import { LEFT_BUTTON, activatedKeyboardDrag, cancelledDrag, escapeKeys,
|
|
13
|
+
import { LEFT_BUTTON, activatedKeyboardDrag, cancelledDrag, escapeKeys, insertionIndex, isCarrying, markDragging, measureLift, movedPointer, phaseOf, pressedDraggable, releasedPointer, } from "../drag/drag.js";
|
|
14
14
|
import * as Glyph from "../glyph/glyph.js";
|
|
15
15
|
import * as IconButton from "../iconButton/iconButton.js";
|
|
16
16
|
import { LOCALE } from "../locale/locale.js";
|
|
@@ -112,7 +112,6 @@ const id = (model) => __foldkitBrandViewResult((model.dragAndDrop.id), "@gjermun
|
|
|
112
112
|
export const isHolding = (model) => __foldkitBrandViewResult((Option.isSome(UiDragAndDrop.maybeDraggedItemId(model.dragAndDrop))), "@gjermundgaraba-designs/pixel-paper/src/sortable/sortable.js#isHolding");
|
|
113
113
|
// DOM HOOKS
|
|
114
114
|
const itemHook = (model, itemId) => __foldkitBrandViewResult((`${id(model)}/${itemId}`), "@gjermundgaraba-designs/pixel-paper/src/sortable/sortable.js#itemHook");
|
|
115
|
-
const ITEM_ATTRIBUTE = "sortable-item";
|
|
116
115
|
const GRIP_ATTRIBUTE = "sortable-grip";
|
|
117
116
|
// COMMAND
|
|
118
117
|
/** Says a step of a move through the page's live region (as `Announcer.Announce` does). */
|
|
@@ -349,30 +348,30 @@ export const SizeLiftedItem = Mount.define("SizeLiftedItem", {
|
|
|
349
348
|
return __foldkitBrandViewResult((Message.CompletedSizeLiftedItem()), "@gjermundgaraba-designs/pixel-paper/src/sortable/sortable.js#anonymous~33");
|
|
350
349
|
})), "@gjermundgaraba-designs/pixel-paper/src/sortable/sortable.js#execute~2"),
|
|
351
350
|
});
|
|
352
|
-
const INNER_CONTROL = innerControls(`[data-${GRIP_ATTRIBUTE}]`);
|
|
353
|
-
/** Whether a press lifts the item: on its grip under any pointer, on its body under a mouse,
|
|
354
|
-
* and never on a control inside it. */
|
|
355
|
-
const isLiftingPress = (target, pointerType) => {
|
|
356
|
-
if (!(target instanceof Element)) {
|
|
357
|
-
return __foldkitBrandViewResult((pointerType === "mouse"), "@gjermundgaraba-designs/pixel-paper/src/sortable/sortable.js#isLiftingPress");
|
|
358
|
-
}
|
|
359
|
-
const isOnGrip = target.closest(`[data-${GRIP_ATTRIBUTE}]`) !== null;
|
|
360
|
-
const maybeItem = Option.fromNullishOr(target.closest(`[data-${ITEM_ATTRIBUTE}]`));
|
|
361
|
-
const isOnControl = Option.exists(maybeItem, (item) => {
|
|
362
|
-
const control = target.closest(INNER_CONTROL);
|
|
363
|
-
return __foldkitBrandViewResult((control !== null && item.contains(control)), "@gjermundgaraba-designs/pixel-paper/src/sortable/sortable.js#anonymous~34");
|
|
364
|
-
});
|
|
365
|
-
return __foldkitBrandViewResult((isOnGrip || (pointerType === "mouse" && !isOnControl)), "@gjermundgaraba-designs/pixel-paper/src/sortable/sortable.js#isLiftingPress");
|
|
366
|
-
};
|
|
367
351
|
const helpId = (model) => __foldkitBrandViewResult((`${id(model)}-grip-help`), "@gjermundgaraba-designs/pixel-paper/src/sortable/sortable.js#helpId");
|
|
368
352
|
const isHeldItem = (model, itemId) => __foldkitBrandViewResult((UiDragAndDrop.isDragging(model.dragAndDrop) &&
|
|
369
353
|
Option.contains(UiDragAndDrop.maybeDraggedItemId(model.dragAndDrop), itemHook(model, itemId))), "@gjermundgaraba-designs/pixel-paper/src/sortable/sortable.js#isHeldItem");
|
|
370
354
|
const liftedGrip = (h) => __foldkitBrandViewResult((h.span([h.Class("iconbtn quiet"), h.AriaHidden(true)], [Glyph.view({ name: "grip" }, h)])), "@gjermundgaraba-designs/pixel-paper/src/sortable/sortable.js#liftedGrip");
|
|
371
|
-
|
|
355
|
+
/** The item's content beside its grip, or, in a layout where the item places its grip,
|
|
356
|
+
* alone. */
|
|
357
|
+
const itemContents = (info, item, render, h) => {
|
|
358
|
+
const body = h.div([h.Class("sortable-body")], info.toItemView(item, render));
|
|
359
|
+
return __foldkitBrandViewResult((info.layout === "Panels" ? [body] : [render.grip, body]), "@gjermundgaraba-designs/pixel-paper/src/sortable/sortable.js#itemContents");
|
|
360
|
+
};
|
|
361
|
+
const liftedView = (info, item, h) => __foldkitBrandViewResult((h.div([h.Class("sortable-item lifted")], itemContents(info, item, { isLifted: true, grip: liftedGrip(h) }, h))), "@gjermundgaraba-designs/pixel-paper/src/sortable/sortable.js#liftedView");
|
|
372
362
|
const gripView = (info, item, h) => {
|
|
373
|
-
const { model, items } = info;
|
|
363
|
+
const { model, isMovable, items } = info;
|
|
374
364
|
const isIdleNow = phaseOf(model.dragAndDrop) === "Idle";
|
|
375
|
-
const onGripKey = h.OnKeyDownPreventDefault((key) => __foldkitBrandViewResult((Option.liftPredicate(Message.PressedGripKey({ itemId: item.id, items }), () => __foldkitBrandViewResult((isIdleNow && (key === " " || key === "Enter")), "@gjermundgaraba-designs/pixel-paper/src/sortable/sortable.js#anonymous~
|
|
365
|
+
const onGripKey = h.OnKeyDownPreventDefault((key) => __foldkitBrandViewResult((Option.liftPredicate(Message.PressedGripKey({ itemId: item.id, items }), () => __foldkitBrandViewResult((isIdleNow && (key === " " || key === "Enter")), "@gjermundgaraba-designs/pixel-paper/src/sortable/sortable.js#anonymous~35"))), "@gjermundgaraba-designs/pixel-paper/src/sortable/sortable.js#anonymous~34"));
|
|
366
|
+
const onGripPress = h.OnPointerDown((_pointerType, button, screenX, screenY, _time, clientX, clientY, pointerId) => __foldkitBrandViewResult((Option.liftPredicate(Message.PressedItem({
|
|
367
|
+
itemId: item.id,
|
|
368
|
+
pointerId,
|
|
369
|
+
screenX,
|
|
370
|
+
screenY,
|
|
371
|
+
clientX,
|
|
372
|
+
clientY,
|
|
373
|
+
items,
|
|
374
|
+
}), () => __foldkitBrandViewResult((isIdleNow && button === LEFT_BUTTON), "@gjermundgaraba-designs/pixel-paper/src/sortable/sortable.js#anonymous~37"))), "@gjermundgaraba-designs/pixel-paper/src/sortable/sortable.js#anonymous~36"));
|
|
376
375
|
// NOTE: @foldkit/ui gap: DragAndDrop's `draggable` bundle forces `role="option"`, which a
|
|
377
376
|
// plain list's items are not. So the grip carries the ui's `data-draggable-id` by hand, and
|
|
378
377
|
// FocusItem finds the grip by it. While moves are held the grip stays, held, so the focus
|
|
@@ -381,75 +380,51 @@ const gripView = (info, item, h) => {
|
|
|
381
380
|
glyph: "grip",
|
|
382
381
|
label: fill(COPY.gripLabel, item.label),
|
|
383
382
|
isQuiet: true,
|
|
384
|
-
isDisabled: !
|
|
383
|
+
isDisabled: !isMovable,
|
|
385
384
|
attributes: [
|
|
386
385
|
h.DataAttribute("draggable-id", itemHook(model, item.id)),
|
|
387
386
|
h.DataAttribute(GRIP_ATTRIBUTE, ""),
|
|
388
387
|
h.AriaRoleDescription("draggable"),
|
|
389
388
|
h.AriaDescribedBy(helpId(model)),
|
|
390
389
|
Button.pressedAttribute(isHeldItem(model, item.id), h),
|
|
391
|
-
...(
|
|
390
|
+
...(isMovable ? [onGripKey, onGripPress] : []),
|
|
392
391
|
],
|
|
393
392
|
}, h)), "@gjermundgaraba-designs/pixel-paper/src/sortable/sortable.js#gripView");
|
|
394
393
|
};
|
|
395
|
-
const itemAttributes = (info, item, h) => {
|
|
396
|
-
const { model, items } = info;
|
|
397
|
-
const isIdleNow = phaseOf(model.dragAndDrop) === "Idle";
|
|
398
|
-
const onPress = h.OnPointerDown((pointerType, button, screenX, screenY, _time, clientX, clientY, pointerId, target) => __foldkitBrandViewResult((Option.liftPredicate(Message.PressedItem({
|
|
399
|
-
itemId: item.id,
|
|
400
|
-
pointerId,
|
|
401
|
-
screenX,
|
|
402
|
-
screenY,
|
|
403
|
-
clientX,
|
|
404
|
-
clientY,
|
|
405
|
-
items,
|
|
406
|
-
}), () => __foldkitBrandViewResult((isIdleNow &&
|
|
407
|
-
model.isMovable &&
|
|
408
|
-
button === LEFT_BUTTON &&
|
|
409
|
-
isLiftingPress(target, pointerType)), "@gjermundgaraba-designs/pixel-paper/src/sortable/sortable.js#anonymous~38"))), "@gjermundgaraba-designs/pixel-paper/src/sortable/sortable.js#anonymous~37"));
|
|
410
|
-
return __foldkitBrandViewResult(([h.DataAttribute(ITEM_ATTRIBUTE, ""), onPress]), "@gjermundgaraba-designs/pixel-paper/src/sortable/sortable.js#itemAttributes");
|
|
411
|
-
};
|
|
412
394
|
/** Where a pointer would drop the held item, marked on the item it would land beside: before
|
|
413
395
|
* the other item at its target, or after the last. None where it already was. */
|
|
414
|
-
const dropMarkOf = (model) => __foldkitBrandViewResult((pipe(model.maybeHeld, Option.filter(() => __foldkitBrandViewResult((phaseOf(model.dragAndDrop) === "PointerDragging"), "@gjermundgaraba-designs/pixel-paper/src/sortable/sortable.js#anonymous~
|
|
415
|
-
const others = Array.filter(held.items, (item) => __foldkitBrandViewResult((item.id !== held.item.id), "@gjermundgaraba-designs/pixel-paper/src/sortable/sortable.js#anonymous~
|
|
396
|
+
const dropMarkOf = (model) => __foldkitBrandViewResult((pipe(model.maybeHeld, Option.filter(() => __foldkitBrandViewResult((phaseOf(model.dragAndDrop) === "PointerDragging"), "@gjermundgaraba-designs/pixel-paper/src/sortable/sortable.js#anonymous~38")), Option.flatMap((held) => __foldkitBrandViewResult((pipe(targetIndex(model, held), Option.filter((index) => __foldkitBrandViewResult((!Option.contains(indexOf(held.items, held.item.id), index)), "@gjermundgaraba-designs/pixel-paper/src/sortable/sortable.js#anonymous~40")), Option.flatMap((index) => {
|
|
397
|
+
const others = Array.filter(held.items, (item) => __foldkitBrandViewResult((item.id !== held.item.id), "@gjermundgaraba-designs/pixel-paper/src/sortable/sortable.js#anonymous~42"));
|
|
416
398
|
return __foldkitBrandViewResult((Option.orElse(Option.map(Array.get(others, index), (item) => (__foldkitBrandViewResult(({
|
|
417
399
|
itemId: item.id,
|
|
418
400
|
side: "Before",
|
|
419
|
-
}), "@gjermundgaraba-designs/pixel-paper/src/sortable/sortable.js#anonymous~
|
|
401
|
+
}), "@gjermundgaraba-designs/pixel-paper/src/sortable/sortable.js#anonymous~43"))), () => __foldkitBrandViewResult((Option.map(Array.last(others), (item) => (__foldkitBrandViewResult(({
|
|
420
402
|
itemId: item.id,
|
|
421
403
|
side: "After",
|
|
422
|
-
}), "@gjermundgaraba-designs/pixel-paper/src/sortable/sortable.js#anonymous~
|
|
423
|
-
}))), "@gjermundgaraba-designs/pixel-paper/src/sortable/sortable.js#anonymous~
|
|
424
|
-
const dropClass = (info, item) => __foldkitBrandViewResult((Option.match(Option.filter(info.maybeDropMark, ({ itemId }) => __foldkitBrandViewResult((itemId === item.id), "@gjermundgaraba-designs/pixel-paper/src/sortable/sortable.js#anonymous~
|
|
404
|
+
}), "@gjermundgaraba-designs/pixel-paper/src/sortable/sortable.js#anonymous~45")))), "@gjermundgaraba-designs/pixel-paper/src/sortable/sortable.js#anonymous~44"))), "@gjermundgaraba-designs/pixel-paper/src/sortable/sortable.js#anonymous~41");
|
|
405
|
+
}))), "@gjermundgaraba-designs/pixel-paper/src/sortable/sortable.js#anonymous~39")))), "@gjermundgaraba-designs/pixel-paper/src/sortable/sortable.js#dropMarkOf");
|
|
406
|
+
const dropClass = (info, item) => __foldkitBrandViewResult((Option.match(Option.filter(info.maybeDropMark, ({ itemId }) => __foldkitBrandViewResult((itemId === item.id), "@gjermundgaraba-designs/pixel-paper/src/sortable/sortable.js#anonymous~46")), {
|
|
425
407
|
onNone: () => __foldkitBrandViewResult((""), "@gjermundgaraba-designs/pixel-paper/src/sortable/sortable.js#onNone~5"),
|
|
426
408
|
onSome: ({ side }) => (__foldkitBrandViewResult((side === "Before" ? " drop-before" : " drop-after"), "@gjermundgaraba-designs/pixel-paper/src/sortable/sortable.js#onSome~5")),
|
|
427
409
|
})), "@gjermundgaraba-designs/pixel-paper/src/sortable/sortable.js#dropClass");
|
|
428
|
-
const itemClass = (info, item) => __foldkitBrandViewResult((`sortable-item${info.
|
|
429
|
-
const itemView = (info, item, h) => __foldkitBrandViewResult((h.keyed("li")(item.id, [
|
|
430
|
-
...UiDragAndDrop.sortable(itemHook(info.model, item.id)),
|
|
431
|
-
...itemAttributes(info, item, h),
|
|
432
|
-
h.Class(itemClass(info, item)),
|
|
433
|
-
], [
|
|
434
|
-
gripView(info, item, h),
|
|
435
|
-
h.div([h.Class("sortable-body")], info.toItemView(item, { isLifted: false })),
|
|
436
|
-
])), "@gjermundgaraba-designs/pixel-paper/src/sortable/sortable.js#itemView");
|
|
410
|
+
const itemClass = (info, item) => __foldkitBrandViewResult((`sortable-item${info.isMovable ? " movable" : ""}${isHeldItem(info.model, item.id) ? " drag-dim" : ""}${dropClass(info, item)}`), "@gjermundgaraba-designs/pixel-paper/src/sortable/sortable.js#itemClass");
|
|
411
|
+
const itemView = (info, item, h) => __foldkitBrandViewResult((h.keyed("li")(item.id, [...UiDragAndDrop.sortable(itemHook(info.model, item.id)), h.Class(itemClass(info, item))], itemContents(info, item, { isLifted: false, grip: gripView(info, item, h) }, h))), "@gjermundgaraba-designs/pixel-paper/src/sortable/sortable.js#itemView");
|
|
437
412
|
/** The items in order, with the held item's copy where the keyboard would drop it: before the
|
|
438
413
|
* other item at its target, or last. The copy carries no `data-sortable-id`, so it is not
|
|
439
414
|
* counted as an item. */
|
|
440
415
|
const itemsView = (info, h) => {
|
|
441
416
|
const { model, items } = info;
|
|
442
|
-
const rows = Array.map(items, (item) => (__foldkitBrandViewResult(({ item, html: itemView(info, item, h) }), "@gjermundgaraba-designs/pixel-paper/src/sortable/sortable.js#anonymous~
|
|
443
|
-
const maybeSlot = pipe(model.maybeHeld, Option.filter(() => __foldkitBrandViewResult((phaseOf(model.dragAndDrop) === "KeyboardDragging"), "@gjermundgaraba-designs/pixel-paper/src/sortable/sortable.js#anonymous~
|
|
417
|
+
const rows = Array.map(items, (item) => (__foldkitBrandViewResult(({ item, html: itemView(info, item, h) }), "@gjermundgaraba-designs/pixel-paper/src/sortable/sortable.js#anonymous~47")));
|
|
418
|
+
const maybeSlot = pipe(model.maybeHeld, Option.filter(() => __foldkitBrandViewResult((phaseOf(model.dragAndDrop) === "KeyboardDragging"), "@gjermundgaraba-designs/pixel-paper/src/sortable/sortable.js#anonymous~48")), Option.flatMap((held) => __foldkitBrandViewResult((Option.map(targetIndex(model, held), (index) => (__foldkitBrandViewResult(({ held, index }), "@gjermundgaraba-designs/pixel-paper/src/sortable/sortable.js#anonymous~50")))), "@gjermundgaraba-designs/pixel-paper/src/sortable/sortable.js#anonymous~49")));
|
|
444
419
|
return __foldkitBrandViewResult((Option.match(maybeSlot, {
|
|
445
|
-
onNone: () => __foldkitBrandViewResult((Array.map(rows, ({ html }) => __foldkitBrandViewResult((html), "@gjermundgaraba-designs/pixel-paper/src/sortable/sortable.js#anonymous~
|
|
420
|
+
onNone: () => __foldkitBrandViewResult((Array.map(rows, ({ html }) => __foldkitBrandViewResult((html), "@gjermundgaraba-designs/pixel-paper/src/sortable/sortable.js#anonymous~51"))), "@gjermundgaraba-designs/pixel-paper/src/sortable/sortable.js#onNone~6"),
|
|
446
421
|
onSome: ({ held, index }) => {
|
|
447
422
|
const slot = h.keyed("li")("sortable-slot", [h.Class("sortable-slot"), h.AriaHidden(true), h.Inert(true)], [liftedView(info, held.item, h)]);
|
|
448
|
-
const others = Array.filter(rows, ({ item }) => __foldkitBrandViewResult((item.id !== held.item.id), "@gjermundgaraba-designs/pixel-paper/src/sortable/sortable.js#anonymous~
|
|
423
|
+
const others = Array.filter(rows, ({ item }) => __foldkitBrandViewResult((item.id !== held.item.id), "@gjermundgaraba-designs/pixel-paper/src/sortable/sortable.js#anonymous~52"));
|
|
449
424
|
const maybeBefore = Array.get(others, index);
|
|
450
425
|
return __foldkitBrandViewResult((Option.match(maybeBefore, {
|
|
451
|
-
onNone: () => __foldkitBrandViewResult(([...Array.map(rows, ({ html }) => __foldkitBrandViewResult((html), "@gjermundgaraba-designs/pixel-paper/src/sortable/sortable.js#anonymous~
|
|
452
|
-
onSome: (before) => __foldkitBrandViewResult((Array.flatMap(rows, ({ item, html }) => __foldkitBrandViewResult((item.id === before.item.id ? [slot, html] : [html]), "@gjermundgaraba-designs/pixel-paper/src/sortable/sortable.js#anonymous~
|
|
426
|
+
onNone: () => __foldkitBrandViewResult(([...Array.map(rows, ({ html }) => __foldkitBrandViewResult((html), "@gjermundgaraba-designs/pixel-paper/src/sortable/sortable.js#anonymous~53")), slot]), "@gjermundgaraba-designs/pixel-paper/src/sortable/sortable.js#onNone~7"),
|
|
427
|
+
onSome: (before) => __foldkitBrandViewResult((Array.flatMap(rows, ({ item, html }) => __foldkitBrandViewResult((item.id === before.item.id ? [slot, html] : [html]), "@gjermundgaraba-designs/pixel-paper/src/sortable/sortable.js#anonymous~54"))), "@gjermundgaraba-designs/pixel-paper/src/sortable/sortable.js#onSome~7"),
|
|
453
428
|
})), "@gjermundgaraba-designs/pixel-paper/src/sortable/sortable.js#onSome~6");
|
|
454
429
|
},
|
|
455
430
|
})), "@gjermundgaraba-designs/pixel-paper/src/sortable/sortable.js#itemsView");
|
|
@@ -459,7 +434,7 @@ const liftedCopyView = (info, h) => {
|
|
|
459
434
|
const maybeLifted = Option.all({
|
|
460
435
|
style: UiDragAndDrop.ghostStyle(model.dragAndDrop),
|
|
461
436
|
held: model.maybeHeld,
|
|
462
|
-
press: Option.flatMap(model.maybeHeld, ({ maybePress }) => __foldkitBrandViewResult((maybePress), "@gjermundgaraba-designs/pixel-paper/src/sortable/sortable.js#anonymous~
|
|
437
|
+
press: Option.flatMap(model.maybeHeld, ({ maybePress }) => __foldkitBrandViewResult((maybePress), "@gjermundgaraba-designs/pixel-paper/src/sortable/sortable.js#anonymous~55")),
|
|
463
438
|
});
|
|
464
439
|
return __foldkitBrandViewResult((Array.fromOption(Option.map(maybeLifted, ({ style, held, press }) => __foldkitBrandViewResult((h.div([
|
|
465
440
|
h.Class("sortable-lift"),
|
|
@@ -471,25 +446,34 @@ const liftedCopyView = (info, h) => {
|
|
|
471
446
|
pressX: press.clientX,
|
|
472
447
|
pressY: press.clientY,
|
|
473
448
|
})),
|
|
474
|
-
], [liftedView(info, held.item, h)])), "@gjermundgaraba-designs/pixel-paper/src/sortable/sortable.js#anonymous~
|
|
449
|
+
], [liftedView(info, held.item, h)])), "@gjermundgaraba-designs/pixel-paper/src/sortable/sortable.js#anonymous~56")))), "@gjermundgaraba-designs/pixel-paper/src/sortable/sortable.js#liftedCopyView");
|
|
475
450
|
};
|
|
451
|
+
const layoutClass = (layout) => __foldkitBrandViewResult((Match.value(layout).pipe(Match.withReturnType(), Match.when("List", () => __foldkitBrandViewResult(("sortable"), "@gjermundgaraba-designs/pixel-paper/src/sortable/sortable.js#anonymous~57")), Match.when("Grid", () => __foldkitBrandViewResult(("sortable grid"), "@gjermundgaraba-designs/pixel-paper/src/sortable/sortable.js#anonymous~58")), Match.when("Panels", () => __foldkitBrandViewResult(("sortable panels"), "@gjermundgaraba-designs/pixel-paper/src/sortable/sortable.js#anonymous~59")), Match.exhaustive)), "@gjermundgaraba-designs/pixel-paper/src/sortable/sortable.js#layoutClass");
|
|
476
452
|
/**
|
|
477
|
-
* Items the reader puts in order, as rows or as
|
|
478
|
-
* `div.sortable` holding `ul.sortable-items` (named by `label`) of
|
|
479
|
-
* grip and the parent's content
|
|
453
|
+
* Items the reader puts in order, as rows, as tiles or as panels, built on `@foldkit/ui`'s
|
|
454
|
+
* DragAndDrop: `div.sortable` holding `ul.sortable-items` (named by `label`) of
|
|
455
|
+
* `li.sortable-item`, each its grip and the parent's content (a panel holds its grip in its
|
|
456
|
+
* title bar). The items are the parent's; a drop reports `Moved`.
|
|
480
457
|
*
|
|
481
|
-
*
|
|
482
|
-
*
|
|
483
|
-
* up, every arrow moves it one place in reading order, Space drops it and Escape puts it back.
|
|
458
|
+
* Only the grip lifts an item, under every pointer, so the item's own text selects and a
|
|
459
|
+
* finger on it scrolls the page; the keyboard takes the grip too: Space or Enter picks the
|
|
460
|
+
* item up, every arrow moves it one place in reading order, Space drops it and Escape puts it back.
|
|
484
461
|
* The held item dims in place; a copy, lifted onto the card shadow, follows the pointer or
|
|
485
462
|
* prints where the keyboard would drop it; each step is said through the page's live region.
|
|
486
463
|
* Lift `subscriptions(keyPrefix)` into the application's, or nothing follows the pointer or
|
|
487
464
|
* the keys.
|
|
488
465
|
*/
|
|
489
466
|
export const view = defineView((model, viewInputs, h) => {
|
|
490
|
-
const { items, label, layout = "List", toItemView } = viewInputs;
|
|
491
|
-
const info = {
|
|
492
|
-
|
|
467
|
+
const { items, label, layout = "List", isReadOnly = false, toItemView } = viewInputs;
|
|
468
|
+
const info = {
|
|
469
|
+
model,
|
|
470
|
+
layout,
|
|
471
|
+
isMovable: model.isMovable && !isReadOnly,
|
|
472
|
+
items,
|
|
473
|
+
maybeDropMark: dropMarkOf(model),
|
|
474
|
+
toItemView,
|
|
475
|
+
};
|
|
476
|
+
return __foldkitBrandViewResult((h.div([h.Class(layoutClass(layout))], [
|
|
493
477
|
// NOTE: Safari takes list semantics from a list printed without markers, so the list
|
|
494
478
|
// says it is one
|
|
495
479
|
h.ul([
|
|
@@ -500,5 +484,5 @@ export const view = defineView((model, viewInputs, h) => {
|
|
|
500
484
|
], itemsView(info, h)),
|
|
501
485
|
h.p([h.Id(helpId(model)), h.Class("sr-only")], [COPY.gripHelp]),
|
|
502
486
|
...liftedCopyView(info, h),
|
|
503
|
-
])), "@gjermundgaraba-designs/pixel-paper/src/sortable/sortable.js#anonymous~
|
|
487
|
+
])), "@gjermundgaraba-designs/pixel-paper/src/sortable/sortable.js#anonymous~60");
|
|
504
488
|
});
|
package/package.json
CHANGED
package/src/panel/panel.ts
CHANGED
|
@@ -14,6 +14,9 @@ export type ViewConfig = Readonly<{
|
|
|
14
14
|
id: string;
|
|
15
15
|
/** Pixel caps in the inverted title bar. */
|
|
16
16
|
title: string;
|
|
17
|
+
/** A quiet IconButton before the title, such as the grip a Sortable of panels hands each
|
|
18
|
+
* panel. On the ink bar it prints as `actions` do. */
|
|
19
|
+
lead?: ReadonlyArray<Html | string>;
|
|
17
20
|
/** A count or state at the title bar's right. */
|
|
18
21
|
meta?: string;
|
|
19
22
|
/** Small Buttons and IconButtons at the title bar's right end, after the meta. On the ink
|
|
@@ -32,7 +35,7 @@ export const titleId = (id: string): string => `${id}-title`;
|
|
|
32
35
|
/** The unit of the system: paper in a 2px ink box on a 5px print shadow, under a title bar. A
|
|
33
36
|
* region landmark (`<section>`) named by its `h2` title, keyed by its `id`. */
|
|
34
37
|
export const view = <Message>(config: ViewConfig, h: HtmlBuilder<Message>): Html => {
|
|
35
|
-
const { id, title, meta, actions = [], body, foot = [], isFlush = false } = config;
|
|
38
|
+
const { id, title, lead = [], meta, actions = [], body, foot = [], isFlush = false } = config;
|
|
36
39
|
|
|
37
40
|
return h.keyed("section")(
|
|
38
41
|
id,
|
|
@@ -41,6 +44,7 @@ export const view = <Message>(config: ViewConfig, h: HtmlBuilder<Message>): Html
|
|
|
41
44
|
h.header(
|
|
42
45
|
[h.Class("panel-tb")],
|
|
43
46
|
[
|
|
47
|
+
...slotView("span", "panel-lead", lead, h),
|
|
44
48
|
h.h2([h.Id(titleId(id))], [title]),
|
|
45
49
|
...(meta === undefined ? [] : [h.span([h.Class("meta")], [meta])]),
|
|
46
50
|
...slotView("span", "panel-acts", actions, h),
|
package/src/pixel-paper.css
CHANGED
|
@@ -983,23 +983,32 @@ a.textbtn {
|
|
|
983
983
|
flex: 1 1 100%;
|
|
984
984
|
}
|
|
985
985
|
|
|
986
|
-
/* ---------- the panel's title-bar actions ---------- */
|
|
986
|
+
/* ---------- the panel's title-bar lead and actions ---------- */
|
|
987
987
|
|
|
988
|
-
/* small buttons and icon buttons at the bar's right end
|
|
989
|
-
them, as a window's does, so a small button keeps
|
|
990
|
-
|
|
988
|
+
/* small buttons and icon buttons at the bar's right end, and an icon button before the
|
|
989
|
+
title; the bar trims its padding around them, as a window's does, so a small button keeps
|
|
990
|
+
the bar near its one-line height */
|
|
991
|
+
.panel-tb:has(> :is(.panel-lead, .panel-acts)) {
|
|
991
992
|
align-items: center;
|
|
992
993
|
padding-block: 3px;
|
|
993
|
-
padding-right: 3px;
|
|
994
994
|
min-height: 31px;
|
|
995
995
|
}
|
|
996
996
|
|
|
997
|
-
.panel-tb:has(> .panel-
|
|
997
|
+
.panel-tb:has(> .panel-lead) {
|
|
998
|
+
padding-left: 3px;
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
.panel-tb:has(> .panel-acts) {
|
|
1002
|
+
padding-right: 3px;
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
.panel-tb:has(> :is(.panel-lead, .panel-acts)) h2 {
|
|
998
1006
|
flex: 1;
|
|
999
1007
|
min-width: 0;
|
|
1000
1008
|
overflow-wrap: anywhere;
|
|
1001
1009
|
}
|
|
1002
1010
|
|
|
1011
|
+
.panel-lead,
|
|
1003
1012
|
.panel-acts {
|
|
1004
1013
|
display: inline-flex;
|
|
1005
1014
|
align-items: center;
|
|
@@ -1014,6 +1023,7 @@ a.textbtn {
|
|
|
1014
1023
|
|
|
1015
1024
|
/* on the ink bar a button prints in paper: an outline, or filled when primary. No print
|
|
1016
1025
|
shadow (it cannot show on ink), so a press nudges 1px, as a toolbar tool does */
|
|
1026
|
+
.panel-lead .btn,
|
|
1017
1027
|
.panel-acts .btn {
|
|
1018
1028
|
color: var(--paper);
|
|
1019
1029
|
background: var(--ink);
|
|
@@ -1026,6 +1036,7 @@ a.textbtn {
|
|
|
1026
1036
|
background: var(--paper);
|
|
1027
1037
|
}
|
|
1028
1038
|
|
|
1039
|
+
.panel-lead .btn:not([aria-disabled="true"]):active,
|
|
1029
1040
|
.panel-acts .btn:not([aria-disabled="true"]):active {
|
|
1030
1041
|
transform: translate(1px, 1px);
|
|
1031
1042
|
box-shadow: none;
|
|
@@ -1038,12 +1049,17 @@ a.textbtn {
|
|
|
1038
1049
|
opacity: 0.6;
|
|
1039
1050
|
}
|
|
1040
1051
|
|
|
1041
|
-
|
|
1052
|
+
/* a quiet icon button prints in paper, and a hover cannot darken it to the ink of the bar */
|
|
1053
|
+
.panel-lead .iconbtn.quiet,
|
|
1054
|
+
.panel-acts .iconbtn.quiet,
|
|
1055
|
+
.panel-lead .iconbtn.quiet:hover,
|
|
1056
|
+
.panel-acts .iconbtn.quiet:hover {
|
|
1042
1057
|
color: inherit;
|
|
1043
1058
|
}
|
|
1044
1059
|
|
|
1045
1060
|
/* the ring sits 4px inside the button in its own text colour, so it reads on an outline and
|
|
1046
1061
|
on a filled one alike */
|
|
1062
|
+
.panel-lead .btn:focus-visible,
|
|
1047
1063
|
.panel-acts .btn:focus-visible {
|
|
1048
1064
|
outline-color: currentColor;
|
|
1049
1065
|
outline-offset: -4px;
|
|
@@ -2692,8 +2708,8 @@ dialog.veil:focus {
|
|
|
2692
2708
|
opacity: 0.75;
|
|
2693
2709
|
}
|
|
2694
2710
|
|
|
2695
|
-
/* a held
|
|
2696
|
-
.card-tb .iconbtn[aria-disabled="true"] {
|
|
2711
|
+
/* a held icon button fades on the bar, as a held button in a panel's title bar does */
|
|
2712
|
+
:is(.card-tb, .panel-tb) .iconbtn[aria-disabled="true"] {
|
|
2697
2713
|
opacity: 0.6;
|
|
2698
2714
|
cursor: default;
|
|
2699
2715
|
}
|
|
@@ -3353,24 +3369,16 @@ dialog.veil:focus {
|
|
|
3353
3369
|
min-width: 0;
|
|
3354
3370
|
}
|
|
3355
3371
|
|
|
3356
|
-
.sortable-item.movable {
|
|
3357
|
-
cursor: grab;
|
|
3358
|
-
}
|
|
3359
|
-
|
|
3360
3372
|
.sortable-item [data-sortable-grip] {
|
|
3361
3373
|
flex: none;
|
|
3362
3374
|
}
|
|
3363
3375
|
|
|
3376
|
+
/* the grip is the only handle: the rest of the item selects its text and scrolls the page */
|
|
3364
3377
|
.sortable-item.movable [data-sortable-grip] {
|
|
3365
3378
|
cursor: grab;
|
|
3366
|
-
/* the grip is where a finger drags; the rest of the item scrolls the page */
|
|
3367
3379
|
touch-action: none;
|
|
3368
3380
|
}
|
|
3369
3381
|
|
|
3370
|
-
.sortable-item.movable:hover {
|
|
3371
|
-
background: var(--head);
|
|
3372
|
-
}
|
|
3373
|
-
|
|
3374
3382
|
.sortable-item.drag-dim {
|
|
3375
3383
|
opacity: 0.4;
|
|
3376
3384
|
}
|
|
@@ -3394,7 +3402,7 @@ dialog.veil:focus {
|
|
|
3394
3402
|
bottom: -1px;
|
|
3395
3403
|
}
|
|
3396
3404
|
|
|
3397
|
-
.sortable.grid .sortable-item:is(.drop-before, .drop-after)::before {
|
|
3405
|
+
.sortable:is(.grid, .panels) .sortable-item:is(.drop-before, .drop-after)::before {
|
|
3398
3406
|
top: -2px;
|
|
3399
3407
|
bottom: -2px;
|
|
3400
3408
|
left: auto;
|
|
@@ -3454,6 +3462,63 @@ dialog.veil:focus {
|
|
|
3454
3462
|
padding: 0;
|
|
3455
3463
|
}
|
|
3456
3464
|
|
|
3465
|
+
/* panels: the parent's own panels, up to three across in the deck's gutter and 300px and up,
|
|
3466
|
+
so two across and then one as the column narrows. A row takes its tallest panel's height,
|
|
3467
|
+
and each panel's foot sits at its bottom */
|
|
3468
|
+
.sortable.panels .sortable-items {
|
|
3469
|
+
display: grid;
|
|
3470
|
+
grid-template-columns: repeat(
|
|
3471
|
+
auto-fill,
|
|
3472
|
+
minmax(min(100%, max(300px, (100% - 2 * var(--gap)) / 3)), 1fr)
|
|
3473
|
+
);
|
|
3474
|
+
gap: var(--gap);
|
|
3475
|
+
}
|
|
3476
|
+
|
|
3477
|
+
.sortable.panels .sortable-item {
|
|
3478
|
+
display: block;
|
|
3479
|
+
padding: 0;
|
|
3480
|
+
border-bottom: 0;
|
|
3481
|
+
background: none;
|
|
3482
|
+
}
|
|
3483
|
+
|
|
3484
|
+
.sortable.panels .sortable-body {
|
|
3485
|
+
height: 100%;
|
|
3486
|
+
}
|
|
3487
|
+
|
|
3488
|
+
.sortable.panels .sortable-body > .panel {
|
|
3489
|
+
display: flex;
|
|
3490
|
+
flex-direction: column;
|
|
3491
|
+
height: 100%;
|
|
3492
|
+
}
|
|
3493
|
+
|
|
3494
|
+
.sortable.panels .sortable-body > .panel > .panel-foot {
|
|
3495
|
+
margin-top: auto;
|
|
3496
|
+
}
|
|
3497
|
+
|
|
3498
|
+
/* the drop line runs down the middle of the gutter */
|
|
3499
|
+
.sortable.panels .sortable-item.drop-before::before {
|
|
3500
|
+
left: calc(var(--gap) / -2 - 1px);
|
|
3501
|
+
}
|
|
3502
|
+
|
|
3503
|
+
.sortable.panels .sortable-item.drop-after::before {
|
|
3504
|
+
right: calc(var(--gap) / -2 - 1px);
|
|
3505
|
+
}
|
|
3506
|
+
|
|
3507
|
+
/* the held copy is the panel itself, tinted head and lifted onto the card shadow */
|
|
3508
|
+
.sortable.panels .sortable-item.lifted {
|
|
3509
|
+
border: 0;
|
|
3510
|
+
box-shadow: none;
|
|
3511
|
+
}
|
|
3512
|
+
|
|
3513
|
+
.sortable.panels .lifted .panel {
|
|
3514
|
+
background: var(--head);
|
|
3515
|
+
box-shadow: var(--shadow-card);
|
|
3516
|
+
}
|
|
3517
|
+
|
|
3518
|
+
.sortable.panels .sortable-slot {
|
|
3519
|
+
padding: 0;
|
|
3520
|
+
}
|
|
3521
|
+
|
|
3457
3522
|
/* while a pointer carries an item, the page grabs and selects nothing */
|
|
3458
3523
|
:root[data-pixel-paper-dragging] {
|
|
3459
3524
|
user-select: none;
|
package/src/sortable/index.ts
CHANGED
package/src/sortable/sortable.ts
CHANGED
|
@@ -16,7 +16,6 @@ import {
|
|
|
16
16
|
activatedKeyboardDrag,
|
|
17
17
|
cancelledDrag,
|
|
18
18
|
escapeKeys,
|
|
19
|
-
innerControls,
|
|
20
19
|
insertionIndex,
|
|
21
20
|
isCarrying,
|
|
22
21
|
markDragging,
|
|
@@ -158,7 +157,6 @@ export const isHolding = (model: Model): boolean =>
|
|
|
158
157
|
|
|
159
158
|
const itemHook = (model: Model, itemId: string): string => `${id(model)}/${itemId}`;
|
|
160
159
|
|
|
161
|
-
const ITEM_ATTRIBUTE = "sortable-item";
|
|
162
160
|
const GRIP_ATTRIBUTE = "sortable-grip";
|
|
163
161
|
|
|
164
162
|
// COMMAND
|
|
@@ -565,14 +563,17 @@ export const SizeLiftedItem = Mount.define("SizeLiftedItem", {
|
|
|
565
563
|
|
|
566
564
|
// VIEW
|
|
567
565
|
|
|
568
|
-
/** How the items lay out: a column of rows,
|
|
569
|
-
export type Layout = "List" | "Grid";
|
|
566
|
+
/** How the items lay out: a column of rows, tiles that wrap, or the parent's own panels. */
|
|
567
|
+
export type Layout = "List" | "Grid" | "Panels";
|
|
570
568
|
|
|
571
569
|
/** What the list hands `toItemView` for each item. */
|
|
572
570
|
export type ItemRender = Readonly<{
|
|
573
571
|
/** This render is the held copy: hidden from screen readers and inert. Leave out ids and
|
|
574
572
|
* anything the page must find once. */
|
|
575
573
|
isLifted: boolean;
|
|
574
|
+
/** The item's grip, for the `"Panels"` layout, where the item places it: in its panel's
|
|
575
|
+
* `lead`. The other layouts print it before the item themselves. */
|
|
576
|
+
grip: Html;
|
|
576
577
|
}>;
|
|
577
578
|
|
|
578
579
|
/** Per-render inputs passed to `view` through `h.submodel`'s `viewInputs`. The `to*` members
|
|
@@ -582,34 +583,21 @@ export type ViewInputs = Readonly<{
|
|
|
582
583
|
/** Names the list for screen readers. */
|
|
583
584
|
label: string;
|
|
584
585
|
/** `"List"` (the default): rows between 1px rules, for a flush panel. `"Grid"`: tiles that
|
|
585
|
-
* wrap, 180px and up.
|
|
586
|
+
* wrap, 180px and up. `"Panels"`: each item is a Panel the parent prints, with `render.grip`
|
|
587
|
+
* in its `lead`, up to three across in the deck's gutter, 300px and up. */
|
|
586
588
|
layout?: Layout;
|
|
589
|
+
/** The reader may look but not reorder, as a read-only sign-in: every grip prints held, as
|
|
590
|
+
* while moves are held, and nothing lifts. */
|
|
591
|
+
isReadOnly?: boolean;
|
|
587
592
|
/** One item's content, beside its grip. Also called for the held copy, with
|
|
588
593
|
* `render.isLifted`. */
|
|
589
594
|
toItemView: (item: Item, render: ItemRender) => ReadonlyArray<Html>;
|
|
590
595
|
}>;
|
|
591
596
|
|
|
592
|
-
const INNER_CONTROL = innerControls(`[data-${GRIP_ATTRIBUTE}]`);
|
|
593
|
-
|
|
594
|
-
/** Whether a press lifts the item: on its grip under any pointer, on its body under a mouse,
|
|
595
|
-
* and never on a control inside it. */
|
|
596
|
-
const isLiftingPress = (target: EventTarget | null, pointerType: string): boolean => {
|
|
597
|
-
if (!(target instanceof Element)) {
|
|
598
|
-
return pointerType === "mouse";
|
|
599
|
-
}
|
|
600
|
-
|
|
601
|
-
const isOnGrip = target.closest(`[data-${GRIP_ATTRIBUTE}]`) !== null;
|
|
602
|
-
const maybeItem = Option.fromNullishOr(target.closest(`[data-${ITEM_ATTRIBUTE}]`));
|
|
603
|
-
const isOnControl = Option.exists(maybeItem, (item) => {
|
|
604
|
-
const control = target.closest(INNER_CONTROL);
|
|
605
|
-
return control !== null && item.contains(control);
|
|
606
|
-
});
|
|
607
|
-
|
|
608
|
-
return isOnGrip || (pointerType === "mouse" && !isOnControl);
|
|
609
|
-
};
|
|
610
|
-
|
|
611
597
|
type ListInfo = Readonly<{
|
|
612
598
|
model: Model;
|
|
599
|
+
layout: Layout;
|
|
600
|
+
isMovable: boolean;
|
|
613
601
|
items: ReadonlyArray<Item>;
|
|
614
602
|
maybeDropMark: Option.Option<DropMark>;
|
|
615
603
|
toItemView: (item: Item, render: ItemRender) => ReadonlyArray<Html>;
|
|
@@ -624,6 +612,18 @@ const isHeldItem = (model: Model, itemId: string): boolean =>
|
|
|
624
612
|
const liftedGrip = <ParentMessage>(h: HtmlBuilder<ParentMessage>): Html =>
|
|
625
613
|
h.span([h.Class("iconbtn quiet"), h.AriaHidden(true)], [Glyph.view({ name: "grip" }, h)]);
|
|
626
614
|
|
|
615
|
+
/** The item's content beside its grip, or, in a layout where the item places its grip,
|
|
616
|
+
* alone. */
|
|
617
|
+
const itemContents = <ParentMessage>(
|
|
618
|
+
info: ListInfo,
|
|
619
|
+
item: Item,
|
|
620
|
+
render: ItemRender,
|
|
621
|
+
h: HtmlBuilder<ParentMessage>,
|
|
622
|
+
): ReadonlyArray<Html> => {
|
|
623
|
+
const body = h.div([h.Class("sortable-body")], info.toItemView(item, render));
|
|
624
|
+
return info.layout === "Panels" ? [body] : [render.grip, body];
|
|
625
|
+
};
|
|
626
|
+
|
|
627
627
|
const liftedView = <ParentMessage>(
|
|
628
628
|
info: ListInfo,
|
|
629
629
|
item: Item,
|
|
@@ -631,11 +631,11 @@ const liftedView = <ParentMessage>(
|
|
|
631
631
|
): Html =>
|
|
632
632
|
h.div(
|
|
633
633
|
[h.Class("sortable-item lifted")],
|
|
634
|
-
|
|
634
|
+
itemContents(info, item, { isLifted: true, grip: liftedGrip(h) }, h),
|
|
635
635
|
);
|
|
636
636
|
|
|
637
637
|
const gripView = (info: ListInfo, item: Item, h: HtmlBuilder<Message>): Html => {
|
|
638
|
-
const { model, items } = info;
|
|
638
|
+
const { model, isMovable, items } = info;
|
|
639
639
|
const isIdleNow = phaseOf(model.dragAndDrop) === "Idle";
|
|
640
640
|
|
|
641
641
|
const onGripKey: Attribute<Message> = h.OnKeyDownPreventDefault((key) =>
|
|
@@ -645,6 +645,22 @@ const gripView = (info: ListInfo, item: Item, h: HtmlBuilder<Message>): Html =>
|
|
|
645
645
|
),
|
|
646
646
|
);
|
|
647
647
|
|
|
648
|
+
const onGripPress: Attribute<Message> = h.OnPointerDown(
|
|
649
|
+
(_pointerType, button, screenX, screenY, _time, clientX, clientY, pointerId) =>
|
|
650
|
+
Option.liftPredicate(
|
|
651
|
+
Message.PressedItem({
|
|
652
|
+
itemId: item.id,
|
|
653
|
+
pointerId,
|
|
654
|
+
screenX,
|
|
655
|
+
screenY,
|
|
656
|
+
clientX,
|
|
657
|
+
clientY,
|
|
658
|
+
items,
|
|
659
|
+
}),
|
|
660
|
+
() => isIdleNow && button === LEFT_BUTTON,
|
|
661
|
+
),
|
|
662
|
+
);
|
|
663
|
+
|
|
648
664
|
// NOTE: @foldkit/ui gap: DragAndDrop's `draggable` bundle forces `role="option"`, which a
|
|
649
665
|
// plain list's items are not. So the grip carries the ui's `data-draggable-id` by hand, and
|
|
650
666
|
// FocusItem finds the grip by it. While moves are held the grip stays, held, so the focus
|
|
@@ -654,51 +670,20 @@ const gripView = (info: ListInfo, item: Item, h: HtmlBuilder<Message>): Html =>
|
|
|
654
670
|
glyph: "grip",
|
|
655
671
|
label: fill(COPY.gripLabel, item.label),
|
|
656
672
|
isQuiet: true,
|
|
657
|
-
isDisabled: !
|
|
673
|
+
isDisabled: !isMovable,
|
|
658
674
|
attributes: [
|
|
659
675
|
h.DataAttribute("draggable-id", itemHook(model, item.id)),
|
|
660
676
|
h.DataAttribute(GRIP_ATTRIBUTE, ""),
|
|
661
677
|
h.AriaRoleDescription("draggable"),
|
|
662
678
|
h.AriaDescribedBy(helpId(model)),
|
|
663
679
|
Button.pressedAttribute(isHeldItem(model, item.id), h),
|
|
664
|
-
...(
|
|
680
|
+
...(isMovable ? [onGripKey, onGripPress] : []),
|
|
665
681
|
],
|
|
666
682
|
},
|
|
667
683
|
h,
|
|
668
684
|
);
|
|
669
685
|
};
|
|
670
686
|
|
|
671
|
-
const itemAttributes = (
|
|
672
|
-
info: ListInfo,
|
|
673
|
-
item: Item,
|
|
674
|
-
h: HtmlBuilder<Message>,
|
|
675
|
-
): ReadonlyArray<Attribute<Message>> => {
|
|
676
|
-
const { model, items } = info;
|
|
677
|
-
const isIdleNow = phaseOf(model.dragAndDrop) === "Idle";
|
|
678
|
-
|
|
679
|
-
const onPress = h.OnPointerDown(
|
|
680
|
-
(pointerType, button, screenX, screenY, _time, clientX, clientY, pointerId, target) =>
|
|
681
|
-
Option.liftPredicate(
|
|
682
|
-
Message.PressedItem({
|
|
683
|
-
itemId: item.id,
|
|
684
|
-
pointerId,
|
|
685
|
-
screenX,
|
|
686
|
-
screenY,
|
|
687
|
-
clientX,
|
|
688
|
-
clientY,
|
|
689
|
-
items,
|
|
690
|
-
}),
|
|
691
|
-
() =>
|
|
692
|
-
isIdleNow &&
|
|
693
|
-
model.isMovable &&
|
|
694
|
-
button === LEFT_BUTTON &&
|
|
695
|
-
isLiftingPress(target, pointerType),
|
|
696
|
-
),
|
|
697
|
-
);
|
|
698
|
-
|
|
699
|
-
return [h.DataAttribute(ITEM_ATTRIBUTE, ""), onPress];
|
|
700
|
-
};
|
|
701
|
-
|
|
702
687
|
type DropMark = Readonly<{ itemId: string; side: "Before" | "After" }>;
|
|
703
688
|
|
|
704
689
|
/** Where a pointer would drop the held item, marked on the item it would land beside: before
|
|
@@ -739,20 +724,13 @@ const dropClass = (info: ListInfo, item: Item): string =>
|
|
|
739
724
|
);
|
|
740
725
|
|
|
741
726
|
const itemClass = (info: ListInfo, item: Item): string =>
|
|
742
|
-
`sortable-item${info.
|
|
727
|
+
`sortable-item${info.isMovable ? " movable" : ""}${isHeldItem(info.model, item.id) ? " drag-dim" : ""}${dropClass(info, item)}`;
|
|
743
728
|
|
|
744
729
|
const itemView = (info: ListInfo, item: Item, h: HtmlBuilder<Message>): Html =>
|
|
745
730
|
h.keyed("li")(
|
|
746
731
|
item.id,
|
|
747
|
-
[
|
|
748
|
-
|
|
749
|
-
...itemAttributes(info, item, h),
|
|
750
|
-
h.Class(itemClass(info, item)),
|
|
751
|
-
],
|
|
752
|
-
[
|
|
753
|
-
gripView(info, item, h),
|
|
754
|
-
h.div([h.Class("sortable-body")], info.toItemView(item, { isLifted: false })),
|
|
755
|
-
],
|
|
732
|
+
[...UiDragAndDrop.sortable(itemHook(info.model, item.id)), h.Class(itemClass(info, item))],
|
|
733
|
+
itemContents(info, item, { isLifted: false, grip: gripView(info, item, h) }, h),
|
|
756
734
|
);
|
|
757
735
|
|
|
758
736
|
/** The items in order, with the held item's copy where the keyboard would drop it: before the
|
|
@@ -819,25 +797,42 @@ const liftedCopyView = (info: ListInfo, h: HtmlBuilder<Message>): ReadonlyArray<
|
|
|
819
797
|
);
|
|
820
798
|
};
|
|
821
799
|
|
|
800
|
+
const layoutClass = (layout: Layout): string =>
|
|
801
|
+
Match.value(layout).pipe(
|
|
802
|
+
Match.withReturnType<string>(),
|
|
803
|
+
Match.when("List", () => "sortable"),
|
|
804
|
+
Match.when("Grid", () => "sortable grid"),
|
|
805
|
+
Match.when("Panels", () => "sortable panels"),
|
|
806
|
+
Match.exhaustive,
|
|
807
|
+
);
|
|
808
|
+
|
|
822
809
|
/**
|
|
823
|
-
* Items the reader puts in order, as rows or as
|
|
824
|
-
* `div.sortable` holding `ul.sortable-items` (named by `label`) of
|
|
825
|
-
* grip and the parent's content
|
|
810
|
+
* Items the reader puts in order, as rows, as tiles or as panels, built on `@foldkit/ui`'s
|
|
811
|
+
* DragAndDrop: `div.sortable` holding `ul.sortable-items` (named by `label`) of
|
|
812
|
+
* `li.sortable-item`, each its grip and the parent's content (a panel holds its grip in its
|
|
813
|
+
* title bar). The items are the parent's; a drop reports `Moved`.
|
|
826
814
|
*
|
|
827
|
-
*
|
|
828
|
-
*
|
|
829
|
-
* up, every arrow moves it one place in reading order, Space drops it and Escape puts it back.
|
|
815
|
+
* Only the grip lifts an item, under every pointer, so the item's own text selects and a
|
|
816
|
+
* finger on it scrolls the page; the keyboard takes the grip too: Space or Enter picks the
|
|
817
|
+
* item up, every arrow moves it one place in reading order, Space drops it and Escape puts it back.
|
|
830
818
|
* The held item dims in place; a copy, lifted onto the card shadow, follows the pointer or
|
|
831
819
|
* prints where the keyboard would drop it; each step is said through the page's live region.
|
|
832
820
|
* Lift `subscriptions(keyPrefix)` into the application's, or nothing follows the pointer or
|
|
833
821
|
* the keys.
|
|
834
822
|
*/
|
|
835
823
|
export const view = defineView<Model, Message, ViewInputs>((model, viewInputs, h): Html => {
|
|
836
|
-
const { items, label, layout = "List", toItemView } = viewInputs;
|
|
837
|
-
const info: ListInfo = {
|
|
824
|
+
const { items, label, layout = "List", isReadOnly = false, toItemView } = viewInputs;
|
|
825
|
+
const info: ListInfo = {
|
|
826
|
+
model,
|
|
827
|
+
layout,
|
|
828
|
+
isMovable: model.isMovable && !isReadOnly,
|
|
829
|
+
items,
|
|
830
|
+
maybeDropMark: dropMarkOf(model),
|
|
831
|
+
toItemView,
|
|
832
|
+
};
|
|
838
833
|
|
|
839
834
|
return h.div(
|
|
840
|
-
[h.Class(layout
|
|
835
|
+
[h.Class(layoutClass(layout))],
|
|
841
836
|
[
|
|
842
837
|
// NOTE: Safari takes list semantics from a list printed without markers, so the list
|
|
843
838
|
// says it is one
|