@expressive/ui 0.0.0-stage → 0.1.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/LICENSE +21 -0
- package/README.md +48 -2
- package/dist/button/base.d.ts +63 -0
- package/dist/button/base.js +90 -0
- package/dist/button/base.js.map +1 -0
- package/dist/button/button.d.ts +56 -0
- package/dist/button/button.js +138 -0
- package/dist/button/button.js.map +1 -0
- package/dist/calendar/base.d.ts +112 -0
- package/dist/calendar/base.js +235 -0
- package/dist/calendar/base.js.map +1 -0
- package/dist/calendar/calendar.d.ts +78 -0
- package/dist/calendar/calendar.js +145 -0
- package/dist/calendar/calendar.js.map +1 -0
- package/dist/calendar/date.js +56 -0
- package/dist/calendar/date.js.map +1 -0
- package/dist/dialog/base.d.ts +74 -0
- package/dist/dialog/base.js +143 -0
- package/dist/dialog/base.js.map +1 -0
- package/dist/dialog/dialog.d.ts +69 -0
- package/dist/dialog/dialog.js +108 -0
- package/dist/dialog/dialog.js.map +1 -0
- package/dist/dom.d.ts +16 -0
- package/dist/dom.js +17 -0
- package/dist/dropzone/base.d.ts +77 -0
- package/dist/dropzone/base.js +159 -0
- package/dist/dropzone/base.js.map +1 -0
- package/dist/dropzone/dropzone.d.ts +38 -0
- package/dist/dropzone/dropzone.js +92 -0
- package/dist/dropzone/dropzone.js.map +1 -0
- package/dist/form/base.d.ts +157 -0
- package/dist/form/base.js +243 -0
- package/dist/form/base.js.map +1 -0
- package/dist/form/form.d.ts +123 -0
- package/dist/form/form.js +179 -0
- package/dist/form/form.js.map +1 -0
- package/dist/icons/caret.d.ts +8 -0
- package/dist/icons/caret.js +16 -0
- package/dist/icons/caret.js.map +1 -0
- package/dist/icons/check.d.ts +8 -0
- package/dist/icons/check.js +16 -0
- package/dist/icons/check.js.map +1 -0
- package/dist/icons/cross.d.ts +8 -0
- package/dist/icons/cross.js +16 -0
- package/dist/icons/cross.js.map +1 -0
- package/dist/icons/icon.js +16 -0
- package/dist/icons/icon.js.map +1 -0
- package/dist/icons/spinner.d.ts +8 -0
- package/dist/icons/spinner.js +27 -0
- package/dist/icons/spinner.js.map +1 -0
- package/dist/icons/upload.d.ts +8 -0
- package/dist/icons/upload.js +16 -0
- package/dist/icons/upload.js.map +1 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +12 -0
- package/dist/layout/base.d.ts +88 -0
- package/dist/layout/base.js +158 -0
- package/dist/layout/base.js.map +1 -0
- package/dist/layout/layout.d.ts +53 -0
- package/dist/layout/layout.js +80 -0
- package/dist/layout/layout.js.map +1 -0
- package/dist/piece.js +13 -0
- package/dist/piece.js.map +1 -0
- package/dist/popover/base.d.ts +94 -0
- package/dist/popover/base.js +176 -0
- package/dist/popover/base.js.map +1 -0
- package/dist/popover/place.d.ts +12 -0
- package/dist/popover/place.js +32 -0
- package/dist/popover/place.js.map +1 -0
- package/dist/popover/popover.d.ts +52 -0
- package/dist/popover/popover.js +104 -0
- package/dist/popover/popover.js.map +1 -0
- package/dist/select/base.d.ts +196 -0
- package/dist/select/base.js +409 -0
- package/dist/select/base.js.map +1 -0
- package/dist/select/select.d.ts +119 -0
- package/dist/select/select.js +219 -0
- package/dist/select/select.js.map +1 -0
- package/dist/table/base.d.ts +96 -0
- package/dist/table/base.js +154 -0
- package/dist/table/base.js.map +1 -0
- package/dist/table/column.d.ts +53 -0
- package/dist/table/column.js +29 -0
- package/dist/table/column.js.map +1 -0
- package/dist/table/table.d.ts +41 -0
- package/dist/table/table.js +101 -0
- package/dist/table/table.js.map +1 -0
- package/package.json +76 -4
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { missing } from "../piece.js";
|
|
2
|
+
import { Component, get } from "@expressive/mvc";
|
|
3
|
+
import { Fragment, childrenOf, jsx, jsxs, propsOf, typeOf } from "@expressive/mvc/jsx-runtime";
|
|
4
|
+
|
|
5
|
+
//#region src/layout/base.tsx
|
|
6
|
+
/**
|
|
7
|
+
* Resizable panes without a look: weights, pointer and keyboard resizing and ARIA, assembled from pieces.
|
|
8
|
+
*
|
|
9
|
+
* `Root` wraps the panes and spreads `rootProps`, styled by `arrange`. `Handle` sits between neighbors and spreads
|
|
10
|
+
* `handleProps(index)`. Both are placeholders that throw when rendered, and `arrange` throws when called - a skin
|
|
11
|
+
* supplies all three.
|
|
12
|
+
*/
|
|
13
|
+
var Layout = class Layout extends Component {
|
|
14
|
+
/** `row` puts panes side by side. Defaults to across the parent `Layout`, else `row`. */
|
|
15
|
+
direction = void 0;
|
|
16
|
+
/** Initial weights per pane; read until the first resize, or when the pane count changes. */
|
|
17
|
+
sizes = void 0;
|
|
18
|
+
/** Handle thickness in pixels. */
|
|
19
|
+
gap = 8;
|
|
20
|
+
/** Smallest a pane can be dragged, in pixels. */
|
|
21
|
+
min = 40;
|
|
22
|
+
/** Weights after a resize. */
|
|
23
|
+
weights = [];
|
|
24
|
+
/** Index of the handle being dragged. */
|
|
25
|
+
dragging = void 0;
|
|
26
|
+
Root = missing("Layout.Root");
|
|
27
|
+
Handle = missing("Layout.Handle");
|
|
28
|
+
parent = get(Layout, false);
|
|
29
|
+
constructor(...args) {
|
|
30
|
+
super(...args, () => () => DRAG.get(this)?.());
|
|
31
|
+
}
|
|
32
|
+
/** Panes in the last render. */
|
|
33
|
+
panes = 0;
|
|
34
|
+
get row() {
|
|
35
|
+
const { direction, parent } = this;
|
|
36
|
+
return direction ? direction == "row" : parent ? !parent.row : true;
|
|
37
|
+
}
|
|
38
|
+
/** For the element holding the panes: its ref, and the style `arrange` gives current weights. */
|
|
39
|
+
get rootProps() {
|
|
40
|
+
const { row, gap, weights, sizes, panes } = this;
|
|
41
|
+
return {
|
|
42
|
+
ref: this.bind,
|
|
43
|
+
style: this.arrange(initial(panes, weights, sizes), row, gap)
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
/** For the separator after pane `index`: role, orientation, position as a percentage, focus and resize handlers. */
|
|
47
|
+
get handleProps() {
|
|
48
|
+
const { row, panes, weights, sizes } = this;
|
|
49
|
+
const current = initial(panes, weights, sizes);
|
|
50
|
+
const total = current.reduce((sum, weight) => sum + weight, 0);
|
|
51
|
+
return (index) => ({
|
|
52
|
+
role: "separator",
|
|
53
|
+
"aria-orientation": row ? "vertical" : "horizontal",
|
|
54
|
+
"aria-label": "Resize",
|
|
55
|
+
"aria-valuemin": 0,
|
|
56
|
+
"aria-valuemax": 100,
|
|
57
|
+
"aria-valuenow": Math.round(current.slice(0, index + 1).reduce((sum, weight) => sum + weight, 0) / total * 100),
|
|
58
|
+
tabIndex: 0,
|
|
59
|
+
onPointerDown: (event) => this.grab(index, event),
|
|
60
|
+
onKeyDown: (event) => this.nudge(index, event)
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
/** Style for the root laying panes out by `weights` along the axis, `gap` pixels apart. A skin supplies it. */
|
|
64
|
+
arrange(weights, row, gap) {
|
|
65
|
+
throw new Error("Layout.arrange has no default - a skin supplies one.");
|
|
66
|
+
}
|
|
67
|
+
/** Length of the root along the axis, in pixels. Defaults to the DOM's layout. */
|
|
68
|
+
measure() {
|
|
69
|
+
const rect = MOUNT.get(this).getBoundingClientRect();
|
|
70
|
+
return this.row ? rect.width : rect.height;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Report pointer moves to `move` until release or cancel, then call `end`; returns a stop for unmount mid-drag.
|
|
74
|
+
* Defaults to `document`.
|
|
75
|
+
*/
|
|
76
|
+
follow(move, end) {
|
|
77
|
+
const stop = () => {
|
|
78
|
+
document.removeEventListener("pointermove", move);
|
|
79
|
+
document.removeEventListener("pointerup", up);
|
|
80
|
+
document.removeEventListener("pointercancel", up);
|
|
81
|
+
};
|
|
82
|
+
const up = () => {
|
|
83
|
+
stop();
|
|
84
|
+
end();
|
|
85
|
+
};
|
|
86
|
+
document.addEventListener("pointermove", move);
|
|
87
|
+
document.addEventListener("pointerup", up);
|
|
88
|
+
document.addEventListener("pointercancel", up);
|
|
89
|
+
return stop;
|
|
90
|
+
}
|
|
91
|
+
/** Move the handle after pane `index` by `delta` pixels. */
|
|
92
|
+
resize(index, delta) {
|
|
93
|
+
const pixels = this.pixels();
|
|
94
|
+
const pair = pixels[index] + pixels[index + 1];
|
|
95
|
+
const before = Math.min(Math.max(pixels[index] + delta, this.min), pair - this.min);
|
|
96
|
+
pixels[index] = before;
|
|
97
|
+
pixels[index + 1] = pair - before;
|
|
98
|
+
this.weights = pixels;
|
|
99
|
+
}
|
|
100
|
+
pixels() {
|
|
101
|
+
const weights = initial(this.panes, this.weights, this.sizes);
|
|
102
|
+
const space = this.measure() - this.gap * (weights.length - 1);
|
|
103
|
+
const total = weights.reduce((sum, weight) => sum + weight, 0);
|
|
104
|
+
return weights.map((weight) => weight * space / total);
|
|
105
|
+
}
|
|
106
|
+
bind(element) {
|
|
107
|
+
if (element) MOUNT.set(this, element);
|
|
108
|
+
}
|
|
109
|
+
/** Start dragging the handle after pane `index`, from pointer down. */
|
|
110
|
+
grab(index, event) {
|
|
111
|
+
if (event.button !== 0) return;
|
|
112
|
+
event.preventDefault();
|
|
113
|
+
let last = this.row ? event.clientX : event.clientY;
|
|
114
|
+
this.dragging = index;
|
|
115
|
+
DRAG.set(this, this.follow((event) => {
|
|
116
|
+
const at = this.row ? event.clientX : event.clientY;
|
|
117
|
+
if (at != last) this.resize(index, at - last);
|
|
118
|
+
last = at;
|
|
119
|
+
}, () => {
|
|
120
|
+
this.dragging = void 0;
|
|
121
|
+
DRAG.delete(this);
|
|
122
|
+
}));
|
|
123
|
+
}
|
|
124
|
+
/** Move the handle after pane `index` by 10px per arrow key along the axis. */
|
|
125
|
+
nudge(index, event) {
|
|
126
|
+
const back = this.row ? "ArrowLeft" : "ArrowUp";
|
|
127
|
+
const forward = this.row ? "ArrowRight" : "ArrowDown";
|
|
128
|
+
if (event.key != back && event.key != forward) return;
|
|
129
|
+
event.preventDefault();
|
|
130
|
+
this.resize(index, event.key == back ? -10 : 10);
|
|
131
|
+
}
|
|
132
|
+
render(props) {
|
|
133
|
+
const { dragging } = this;
|
|
134
|
+
const panes = flatten(props.children);
|
|
135
|
+
this.panes = panes.length;
|
|
136
|
+
return /* @__PURE__ */ jsx(this.Root, { children: panes.map((pane, index) => jsxs(Fragment, { children: [index > 0 && /* @__PURE__ */ jsx(this.Handle, {
|
|
137
|
+
index: index - 1,
|
|
138
|
+
active: dragging === index - 1
|
|
139
|
+
}), pane] }, index)) });
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
function initial(count, weights, sizes) {
|
|
143
|
+
if (weights.length == count) return weights.slice();
|
|
144
|
+
if (sizes && sizes.length == count) return sizes.slice();
|
|
145
|
+
return Array.from({ length: count }, () => 1);
|
|
146
|
+
}
|
|
147
|
+
const MOUNT = /* @__PURE__ */ new WeakMap();
|
|
148
|
+
const DRAG = /* @__PURE__ */ new WeakMap();
|
|
149
|
+
function flatten(children) {
|
|
150
|
+
const out = [];
|
|
151
|
+
for (const node of childrenOf(children)) if (typeOf(node) === Fragment) out.push(...flatten(propsOf(node).children));
|
|
152
|
+
else out.push(node);
|
|
153
|
+
return out;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
//#endregion
|
|
157
|
+
export { Layout };
|
|
158
|
+
//# sourceMappingURL=base.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"base.js","names":[],"sources":["../../src/layout/base.tsx"],"sourcesContent":["import { Component, get, type Model } from '@expressive/mvc';\nimport { Fragment, childrenOf, jsxs, propsOf, typeOf } from '@expressive/mvc/jsx-runtime';\n\nimport { missing } from '../piece';\n\ndeclare namespace Layout {\n type Direction = 'row' | 'column';\n\n interface HandleProps {\n /** Index of the pane before this handle. */\n index: number;\n /** True while this handle is being dragged. */\n active: boolean;\n }\n\n type Key = Pick<KeyboardEvent, 'key' | 'preventDefault'>;\n type Point = Pick<PointerEvent, 'clientX' | 'clientY'>;\n type Pointer = Point & Pick<PointerEvent, 'button' | 'preventDefault'>;\n type Style = Record<string, string>;\n\n type Root = (props: { children: Component.Node }) => Component.Node;\n type Handle = (props: HandleProps) => Component.Node;\n}\n\n/**\n * Resizable panes without a look: weights, pointer and keyboard resizing and ARIA, assembled from pieces.\n *\n * `Root` wraps the panes and spreads `rootProps`, styled by `arrange`. `Handle` sits between neighbors and spreads\n * `handleProps(index)`. Both are placeholders that throw when rendered, and `arrange` throws when called - a skin\n * supplies all three.\n */\nclass Layout extends Component {\n /** `row` puts panes side by side. Defaults to across the parent `Layout`, else `row`. */\n direction?: Layout.Direction = undefined;\n\n /** Initial weights per pane; read until the first resize, or when the pane count changes. */\n sizes?: readonly number[] = undefined;\n\n /** Handle thickness in pixels. */\n gap = 8;\n\n /** Smallest a pane can be dragged, in pixels. */\n min = 40;\n\n /** Weights after a resize. */\n weights: number[] = [];\n\n /** Index of the handle being dragged. */\n dragging?: number = undefined;\n\n Root: Layout.Root = missing('Layout.Root');\n Handle: Layout.Handle = missing('Layout.Handle');\n\n protected parent?: Layout = get(Layout, false);\n\n constructor(...args: Model.Args) {\n super(...args, () => () => DRAG.get(this)?.());\n }\n\n /** Panes in the last render. */\n protected panes = 0;\n\n get row(): boolean {\n const { direction, parent } = this;\n\n return direction ? direction == 'row' : parent ? !parent.row : true;\n }\n\n /** For the element holding the panes: its ref, and the style `arrange` gives current weights. */\n get rootProps() {\n const { row, gap, weights, sizes, panes } = this;\n\n return { ref: this.bind, style: this.arrange(initial(panes, weights, sizes), row, gap) };\n }\n\n /** For the separator after pane `index`: role, orientation, position as a percentage, focus and resize handlers. */\n get handleProps() {\n const { row, panes, weights, sizes } = this;\n const current = initial(panes, weights, sizes);\n const total = current.reduce((sum, weight) => sum + weight, 0);\n\n return (index: number) =>\n ({\n role: 'separator',\n 'aria-orientation': row ? 'vertical' : 'horizontal',\n 'aria-label': 'Resize',\n 'aria-valuemin': 0,\n 'aria-valuemax': 100,\n 'aria-valuenow': Math.round((current.slice(0, index + 1).reduce((sum, weight) => sum + weight, 0) / total) * 100),\n tabIndex: 0,\n onPointerDown: (event: Layout.Pointer) => this.grab(index, event),\n onKeyDown: (event: Layout.Key) => this.nudge(index, event)\n }) as const;\n }\n\n /** Style for the root laying panes out by `weights` along the axis, `gap` pixels apart. A skin supplies it. */\n protected arrange(weights: number[], row: boolean, gap: number): Layout.Style {\n throw new Error('Layout.arrange has no default - a skin supplies one.');\n }\n\n /** Length of the root along the axis, in pixels. Defaults to the DOM's layout. */\n protected measure(): number {\n const rect = MOUNT.get(this)!.getBoundingClientRect();\n\n return this.row ? rect.width : rect.height;\n }\n\n /**\n * Report pointer moves to `move` until release or cancel, then call `end`; returns a stop for unmount mid-drag.\n * Defaults to `document`.\n */\n protected follow(move: (event: Layout.Point) => void, end: () => void): () => void {\n const stop = () => {\n document.removeEventListener('pointermove', move);\n document.removeEventListener('pointerup', up);\n document.removeEventListener('pointercancel', up);\n };\n\n const up = () => {\n stop();\n end();\n };\n\n document.addEventListener('pointermove', move);\n document.addEventListener('pointerup', up);\n document.addEventListener('pointercancel', up);\n\n return stop;\n }\n\n /** Move the handle after pane `index` by `delta` pixels. */\n resize(index: number, delta: number) {\n const pixels = this.pixels();\n const pair = pixels[index] + pixels[index + 1];\n const before = Math.min(Math.max(pixels[index] + delta, this.min), pair - this.min);\n\n pixels[index] = before;\n pixels[index + 1] = pair - before;\n\n this.weights = pixels;\n }\n\n private pixels() {\n const weights = initial(this.panes, this.weights, this.sizes);\n const space = this.measure() - this.gap * (weights.length - 1);\n const total = weights.reduce((sum, weight) => sum + weight, 0);\n\n return weights.map((weight) => (weight * space) / total);\n }\n\n private bind(element: HTMLElement | null) {\n if (element) MOUNT.set(this, element);\n }\n\n /** Start dragging the handle after pane `index`, from pointer down. */\n grab(index: number, event: Layout.Pointer) {\n if (event.button !== 0) return;\n\n event.preventDefault();\n\n let last = this.row ? event.clientX : event.clientY;\n\n this.dragging = index;\n DRAG.set(this, this.follow(\n (event) => {\n const at = this.row ? event.clientX : event.clientY;\n\n if (at != last) this.resize(index, at - last);\n\n last = at;\n },\n () => {\n this.dragging = undefined;\n DRAG.delete(this);\n }\n ));\n }\n\n /** Move the handle after pane `index` by 10px per arrow key along the axis. */\n nudge(index: number, event: Layout.Key) {\n const back = this.row ? 'ArrowLeft' : 'ArrowUp';\n const forward = this.row ? 'ArrowRight' : 'ArrowDown';\n\n if (event.key != back && event.key != forward) return;\n\n event.preventDefault();\n this.resize(index, event.key == back ? -10 : 10);\n }\n\n render(props: { children?: Component.Node }): Component.Node {\n const { dragging } = this;\n const panes = flatten(props.children);\n\n this.panes = panes.length;\n\n return (\n <this.Root>\n {panes.map((pane, index) =>\n jsxs(\n Fragment,\n {\n children: [\n index > 0 && <this.Handle index={index - 1} active={dragging === index - 1} />,\n pane\n ]\n },\n index\n )\n )}\n </this.Root>\n );\n }\n}\n\nfunction initial(count: number, weights: readonly number[], sizes?: readonly number[]): number[] {\n if (weights.length == count) return weights.slice();\n if (sizes && sizes.length == count) return sizes.slice();\n\n return Array.from({ length: count }, () => 1);\n}\n\nconst MOUNT = new WeakMap<Layout, HTMLElement>();\n\nconst DRAG = new WeakMap<Layout, () => void>();\n\nfunction flatten(children: Component.Node): Component.Node[] {\n const out: Component.Node[] = [];\n\n for (const node of childrenOf(children))\n if (typeOf(node) === Fragment) out.push(...flatten((propsOf(node) as any).children));\n else out.push(node);\n\n return out;\n}\n\nexport { Layout };\n"],"mappings":"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"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { Layout as Layout$1 } from "./base.js";
|
|
2
|
+
import { Component } from "@expressive/mvc";
|
|
3
|
+
|
|
4
|
+
//#region src/layout/layout.d.ts
|
|
5
|
+
declare namespace Layout {
|
|
6
|
+
type Direction = Layout$1.Direction;
|
|
7
|
+
type HandleProps = Layout$1.HandleProps;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Resizable panes: each child is a pane, with a draggable handle between neighbors.
|
|
11
|
+
*
|
|
12
|
+
* Panes share space by weight - `sizes`, else equal - and keep at least `min` pixels. A nested `Layout` runs
|
|
13
|
+
* across its parent unless given a `direction`. Handles are focusable separators: arrow keys move them by 10px.
|
|
14
|
+
* Pieces - `Root`, `Handle` - are function components reading `Layout.get()`, each styled on its own; pass one as
|
|
15
|
+
* a prop or assign it in a subclass to replace it, spreading `rootProps` or `handleProps(index)`. Stock
|
|
16
|
+
* pieces are statics.
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* <Layout sizes={[1, 3]}>
|
|
20
|
+
* <nav>…</nav>
|
|
21
|
+
* <Layout>
|
|
22
|
+
* <main>…</main>
|
|
23
|
+
* <footer>…</footer>
|
|
24
|
+
* </Layout>
|
|
25
|
+
* </Layout>
|
|
26
|
+
*/
|
|
27
|
+
declare class Layout extends Layout$1 {
|
|
28
|
+
static Root: ({
|
|
29
|
+
children
|
|
30
|
+
}: {
|
|
31
|
+
children: Component.Node;
|
|
32
|
+
}) => Component.Node;
|
|
33
|
+
static Handle: ({
|
|
34
|
+
index,
|
|
35
|
+
active
|
|
36
|
+
}: Layout.HandleProps) => Component.Node;
|
|
37
|
+
Root: ({
|
|
38
|
+
children
|
|
39
|
+
}: {
|
|
40
|
+
children: Component.Node;
|
|
41
|
+
}) => Component.Node;
|
|
42
|
+
Handle: ({
|
|
43
|
+
index,
|
|
44
|
+
active
|
|
45
|
+
}: Layout.HandleProps) => Component.Node;
|
|
46
|
+
protected arrange(weights: number[], row: boolean, gap: number): {
|
|
47
|
+
[x: string]: string;
|
|
48
|
+
display: string;
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
//#endregion
|
|
52
|
+
export { Layout };
|
|
53
|
+
//# sourceMappingURL=layout.d.ts.map
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { Layout as Layout$1 } from "./base.js";
|
|
2
|
+
import { style } from "@expressive/dom";
|
|
3
|
+
import { jsx } from "@expressive/dom/jsx-runtime";
|
|
4
|
+
|
|
5
|
+
//#region src/layout/layout.tsx
|
|
6
|
+
const Root = ({ children }) => /* @__PURE__ */ jsx("div", {
|
|
7
|
+
...Layout$1.get().rootProps,
|
|
8
|
+
children
|
|
9
|
+
});
|
|
10
|
+
style(Root, {
|
|
11
|
+
minWidth: "0",
|
|
12
|
+
minHeight: "0",
|
|
13
|
+
height: "100%"
|
|
14
|
+
});
|
|
15
|
+
const Handle = ({ index, active }) => {
|
|
16
|
+
const { row, handleProps } = Layout$1.get();
|
|
17
|
+
return /* @__PURE__ */ jsx("div", {
|
|
18
|
+
...handleProps(index),
|
|
19
|
+
_row: row,
|
|
20
|
+
_active: active,
|
|
21
|
+
children: active && /* @__PURE__ */ jsx("div", { _mask: true })
|
|
22
|
+
});
|
|
23
|
+
};
|
|
24
|
+
style(Handle, {
|
|
25
|
+
position: "relative",
|
|
26
|
+
cursor: "row-resize",
|
|
27
|
+
outline: "0",
|
|
28
|
+
touchAction: "none",
|
|
29
|
+
_row: { cursor: "col-resize" },
|
|
30
|
+
"::after": {
|
|
31
|
+
content: "\"\"",
|
|
32
|
+
position: "absolute",
|
|
33
|
+
inset: "2px",
|
|
34
|
+
borderRadius: "4px",
|
|
35
|
+
transition: "background 100ms"
|
|
36
|
+
},
|
|
37
|
+
":hover::after": { background: "var(--ui-highlight, #deebff)" },
|
|
38
|
+
":focus-visible::after": { background: "var(--ui-accent, #2684ff)" },
|
|
39
|
+
_active: { "::after": { background: "var(--ui-accent, #2684ff)" } },
|
|
40
|
+
_mask: {
|
|
41
|
+
position: "fixed",
|
|
42
|
+
inset: "0",
|
|
43
|
+
zIndex: "1000"
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
/**
|
|
47
|
+
* Resizable panes: each child is a pane, with a draggable handle between neighbors.
|
|
48
|
+
*
|
|
49
|
+
* Panes share space by weight - `sizes`, else equal - and keep at least `min` pixels. A nested `Layout` runs
|
|
50
|
+
* across its parent unless given a `direction`. Handles are focusable separators: arrow keys move them by 10px.
|
|
51
|
+
* Pieces - `Root`, `Handle` - are function components reading `Layout.get()`, each styled on its own; pass one as
|
|
52
|
+
* a prop or assign it in a subclass to replace it, spreading `rootProps` or `handleProps(index)`. Stock
|
|
53
|
+
* pieces are statics.
|
|
54
|
+
*
|
|
55
|
+
* @example
|
|
56
|
+
* <Layout sizes={[1, 3]}>
|
|
57
|
+
* <nav>…</nav>
|
|
58
|
+
* <Layout>
|
|
59
|
+
* <main>…</main>
|
|
60
|
+
* <footer>…</footer>
|
|
61
|
+
* </Layout>
|
|
62
|
+
* </Layout>
|
|
63
|
+
*/
|
|
64
|
+
var Layout = class extends Layout$1 {
|
|
65
|
+
static Root = Root;
|
|
66
|
+
static Handle = Handle;
|
|
67
|
+
Root = Root;
|
|
68
|
+
Handle = Handle;
|
|
69
|
+
arrange(weights, row, gap) {
|
|
70
|
+
const tracks = weights.map((weight) => `minmax(0, ${weight}fr)`).join(` ${gap}px `);
|
|
71
|
+
return {
|
|
72
|
+
display: "grid",
|
|
73
|
+
[row ? "gridTemplateColumns" : "gridTemplateRows"]: tracks
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
//#endregion
|
|
79
|
+
export { Layout };
|
|
80
|
+
//# sourceMappingURL=layout.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"layout.js","names":["Base.Layout"],"sources":["../../src/layout/layout.tsx"],"sourcesContent":["/** @jsxImportSource @expressive/dom */\nimport { Component } from '@expressive/mvc';\nimport { style } from '@expressive/dom';\n\nimport * as Base from './base';\n\ndeclare namespace Layout {\n type Direction = Base.Layout.Direction;\n type HandleProps = Base.Layout.HandleProps;\n}\n\nconst Root = ({ children }: { children: Component.Node }): Component.Node => (\n <div {...Base.Layout.get().rootProps}>{children}</div>\n);\n\nstyle(Root, {\n minWidth: '0',\n minHeight: '0',\n height: '100%'\n});\n\nconst Handle = ({ index, active }: Layout.HandleProps): Component.Node => {\n const { row, handleProps } = Base.Layout.get();\n\n return (\n <div {...handleProps(index)} _row={row} _active={active}>\n {active && <div _mask />}\n </div>\n );\n};\n\nstyle(Handle, {\n position: 'relative',\n cursor: 'row-resize',\n outline: '0',\n touchAction: 'none',\n _row: {\n cursor: 'col-resize'\n },\n '::after': {\n content: '\"\"',\n position: 'absolute',\n inset: '2px',\n borderRadius: '4px',\n transition: 'background 100ms'\n },\n ':hover::after': {\n background: 'var(--ui-highlight, #deebff)'\n },\n ':focus-visible::after': {\n background: 'var(--ui-accent, #2684ff)'\n },\n _active: {\n '::after': {\n background: 'var(--ui-accent, #2684ff)'\n }\n },\n _mask: {\n position: 'fixed',\n inset: '0',\n zIndex: '1000'\n }\n});\n\n/**\n * Resizable panes: each child is a pane, with a draggable handle between neighbors.\n *\n * Panes share space by weight - `sizes`, else equal - and keep at least `min` pixels. A nested `Layout` runs\n * across its parent unless given a `direction`. Handles are focusable separators: arrow keys move them by 10px.\n * Pieces - `Root`, `Handle` - are function components reading `Layout.get()`, each styled on its own; pass one as\n * a prop or assign it in a subclass to replace it, spreading `rootProps` or `handleProps(index)`. Stock\n * pieces are statics.\n *\n * @example\n * <Layout sizes={[1, 3]}>\n * <nav>…</nav>\n * <Layout>\n * <main>…</main>\n * <footer>…</footer>\n * </Layout>\n * </Layout>\n */\nclass Layout extends Base.Layout {\n static Root = Root;\n static Handle = Handle;\n\n Root = Root;\n Handle = Handle;\n\n protected arrange(weights: number[], row: boolean, gap: number) {\n const tracks = weights.map((weight) => `minmax(0, ${weight}fr)`).join(` ${gap}px `);\n\n return { display: 'grid', [row ? 'gridTemplateColumns' : 'gridTemplateRows']: tracks };\n }\n}\n\nexport { Handle, Layout, Root };\n"],"mappings":";;;;;AAWA,MAAM,QAAQ,EAAE,eACd,oBAAC;CAAI,YAAgB,KAAK,CAAC;CAAY;EAAe;AAGxD,MAAM,MAAM;CACV,UAAU;CACV,WAAW;CACX,QAAQ;CACT,CAAC;AAEF,MAAM,UAAU,EAAE,OAAO,aAAiD;CACxE,MAAM,EAAE,KAAK,yBAA4B,KAAK;AAE9C,QACE,oBAAC;EAAI,GAAI,YAAY,MAAM;EAAE,MAAM;EAAK,SAAS;YAC9C,UAAU,oBAAC,SAAI,cAAQ;GACpB;;AAIV,MAAM,QAAQ;CACZ,UAAU;CACV,QAAQ;CACR,SAAS;CACT,aAAa;CACb,MAAM,EACJ,QAAQ,cACT;CACD,WAAW;EACT,SAAS;EACT,UAAU;EACV,OAAO;EACP,cAAc;EACd,YAAY;EACb;CACD,iBAAiB,EACf,YAAY,gCACb;CACD,yBAAyB,EACvB,YAAY,6BACb;CACD,SAAS,EACP,WAAW,EACT,YAAY,6BACb,EACF;CACD,OAAO;EACL,UAAU;EACV,OAAO;EACP,QAAQ;EACT;CACF,CAAC;;;;;;;;;;;;;;;;;;;AAoBF,IAAM,SAAN,cAAqBA,SAAY;CAC/B,OAAO,OAAO;CACd,OAAO,SAAS;CAEhB,OAAO;CACP,SAAS;CAET,AAAU,QAAQ,SAAmB,KAAc,KAAa;EAC9D,MAAM,SAAS,QAAQ,KAAK,WAAW,aAAa,OAAO,KAAK,CAAC,KAAK,IAAI,IAAI,KAAK;AAEnF,SAAO;GAAE,SAAS;IAAS,MAAM,wBAAwB,qBAAqB;GAAQ"}
|
package/dist/piece.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
//#region src/piece.ts
|
|
2
|
+
/** Placeholder for a piece a skin must provide; throws naming it when rendered. */
|
|
3
|
+
function missing(piece) {
|
|
4
|
+
return () => {
|
|
5
|
+
throw new Error(`${piece} has no default - assign one in a subclass or pass it as a prop.`);
|
|
6
|
+
};
|
|
7
|
+
}
|
|
8
|
+
/** Default for a piece a skin may leave out. */
|
|
9
|
+
const none = () => null;
|
|
10
|
+
|
|
11
|
+
//#endregion
|
|
12
|
+
export { missing, none };
|
|
13
|
+
//# sourceMappingURL=piece.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"piece.js","names":[],"sources":["../src/piece.ts"],"sourcesContent":["import type { Component } from '@expressive/mvc';\n\n/** Placeholder for a piece a skin must provide; throws naming it when rendered. */\nexport function missing(piece: string) {\n return (): Component.Node => {\n throw new Error(`${piece} has no default - assign one in a subclass or pass it as a prop.`);\n };\n}\n\n/** Default for a piece a skin may leave out. */\nexport const none = (): Component.Node => null;\n"],"mappings":";;AAGA,SAAgB,QAAQ,OAAe;AACrC,cAA6B;AAC3B,QAAM,IAAI,MAAM,GAAG,MAAM,kEAAkE;;;;AAK/F,MAAa,aAA6B"}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { Place, Side } from "./place.js";
|
|
2
|
+
import { Component, Model } from "@expressive/mvc";
|
|
3
|
+
|
|
4
|
+
//#region src/popover/base.d.ts
|
|
5
|
+
declare namespace Popover {
|
|
6
|
+
export { Place, Side };
|
|
7
|
+
export type Wrap = (props: {
|
|
8
|
+
children: Component.Node;
|
|
9
|
+
}) => Component.Node;
|
|
10
|
+
export type Part = () => Component.Node;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Anchored, non-modal panel behavior without a look: toggling, placement, dismissal and ARIA, assembled from pieces.
|
|
14
|
+
*
|
|
15
|
+
* Every visible piece is a placeholder that throws when rendered - a skin assigns each one it renders. `Layer` renders
|
|
16
|
+
* in place by default; a skin replaces it to move the panel out of the page. `Arrow` renders nothing unless assigned.
|
|
17
|
+
* Pieces spread the matching `*Props` getter onto their element to stay accessible and placed.
|
|
18
|
+
*/
|
|
19
|
+
declare class Popover extends Component {
|
|
20
|
+
/** Content of the button. */
|
|
21
|
+
label: Component.Node;
|
|
22
|
+
open: boolean;
|
|
23
|
+
onToggle?: (open: boolean) => void;
|
|
24
|
+
/** Preferred side of the button. */
|
|
25
|
+
side: Popover.Side;
|
|
26
|
+
/** The button's id; prefixes the panel's. */
|
|
27
|
+
id: string;
|
|
28
|
+
/** Where the panel sits, once measured. */
|
|
29
|
+
place?: Popover.Place;
|
|
30
|
+
Trigger: Popover.Part;
|
|
31
|
+
Layer: Popover.Wrap;
|
|
32
|
+
Panel: Popover.Wrap;
|
|
33
|
+
Arrow: Popover.Part;
|
|
34
|
+
constructor(...args: Model.Args);
|
|
35
|
+
get triggerProps(): {
|
|
36
|
+
readonly ref: (element: HTMLElement | null) => void;
|
|
37
|
+
readonly type: "button";
|
|
38
|
+
readonly id: string;
|
|
39
|
+
readonly 'aria-haspopup': "dialog";
|
|
40
|
+
readonly 'aria-expanded': boolean;
|
|
41
|
+
readonly 'aria-controls': string | undefined;
|
|
42
|
+
readonly onClick: () => void;
|
|
43
|
+
};
|
|
44
|
+
/** For the panel: hidden at the origin until measured, then at `place`. */
|
|
45
|
+
get panelProps(): {
|
|
46
|
+
readonly ref: (element: HTMLElement | null) => void;
|
|
47
|
+
readonly id: `${string}-panel`;
|
|
48
|
+
readonly role: "dialog";
|
|
49
|
+
readonly 'aria-labelledby': string;
|
|
50
|
+
readonly tabIndex: -1;
|
|
51
|
+
readonly style: {
|
|
52
|
+
left: string;
|
|
53
|
+
top: string;
|
|
54
|
+
opacity?: undefined;
|
|
55
|
+
} | {
|
|
56
|
+
left: string;
|
|
57
|
+
top: string;
|
|
58
|
+
opacity: string;
|
|
59
|
+
};
|
|
60
|
+
};
|
|
61
|
+
/** For the arrow, once placed, with `above` for styling. */
|
|
62
|
+
get arrowProps(): {
|
|
63
|
+
readonly above: boolean;
|
|
64
|
+
readonly props: {
|
|
65
|
+
readonly 'aria-hidden': "true";
|
|
66
|
+
readonly style: {
|
|
67
|
+
readonly left: `${number}px`;
|
|
68
|
+
};
|
|
69
|
+
};
|
|
70
|
+
};
|
|
71
|
+
toggle(): void;
|
|
72
|
+
/** Close, returning focus to the button when `restore`. */
|
|
73
|
+
close(restore?: boolean): void;
|
|
74
|
+
private change;
|
|
75
|
+
/**
|
|
76
|
+
* Close on a press outside, Escape or focus leaving, and follow scroll and resize, while open; returns the release.
|
|
77
|
+
* Defaults to `document` and `window`.
|
|
78
|
+
*/
|
|
79
|
+
protected listen(): () => void;
|
|
80
|
+
/** Where the panel fits beside the button, or `undefined` before both mount. Defaults to the DOM's layout. */
|
|
81
|
+
protected measure(): Popover.Place | undefined;
|
|
82
|
+
/** Re-measure and move the panel when its place changed; for a `listen` that follows layout. */
|
|
83
|
+
protected reposition(): void;
|
|
84
|
+
/** Ref for the element the panel anchors to, in `triggerProps`. */
|
|
85
|
+
anchor(element: HTMLElement | null): void;
|
|
86
|
+
/** Ref for the panel, in `panelProps`. */
|
|
87
|
+
mount(element: HTMLElement | null): void;
|
|
88
|
+
render(props: {
|
|
89
|
+
children?: Component.Node;
|
|
90
|
+
}): Component.Node;
|
|
91
|
+
}
|
|
92
|
+
//#endregion
|
|
93
|
+
export { Popover };
|
|
94
|
+
//# sourceMappingURL=base.d.ts.map
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import { missing, none } from "../piece.js";
|
|
2
|
+
import { place } from "./place.js";
|
|
3
|
+
import { Component } from "@expressive/mvc";
|
|
4
|
+
import { Fragment, jsx, jsxs } from "@expressive/mvc/jsx-runtime";
|
|
5
|
+
|
|
6
|
+
//#region src/popover/base.tsx
|
|
7
|
+
const TRIGGER = /* @__PURE__ */ new WeakMap();
|
|
8
|
+
const PANEL = /* @__PURE__ */ new WeakMap();
|
|
9
|
+
let ID = 0;
|
|
10
|
+
const Layer = ({ children }) => children;
|
|
11
|
+
/**
|
|
12
|
+
* Anchored, non-modal panel behavior without a look: toggling, placement, dismissal and ARIA, assembled from pieces.
|
|
13
|
+
*
|
|
14
|
+
* Every visible piece is a placeholder that throws when rendered - a skin assigns each one it renders. `Layer` renders
|
|
15
|
+
* in place by default; a skin replaces it to move the panel out of the page. `Arrow` renders nothing unless assigned.
|
|
16
|
+
* Pieces spread the matching `*Props` getter onto their element to stay accessible and placed.
|
|
17
|
+
*/
|
|
18
|
+
var Popover = class extends Component {
|
|
19
|
+
/** Content of the button. */
|
|
20
|
+
label = "Open";
|
|
21
|
+
open = false;
|
|
22
|
+
onToggle = void 0;
|
|
23
|
+
/** Preferred side of the button. */
|
|
24
|
+
side = "below";
|
|
25
|
+
/** The button's id; prefixes the panel's. */
|
|
26
|
+
id = `popover-${++ID}`;
|
|
27
|
+
/** Where the panel sits, once measured. */
|
|
28
|
+
place = void 0;
|
|
29
|
+
Trigger = missing("Popover.Trigger");
|
|
30
|
+
Layer = Layer;
|
|
31
|
+
Panel = missing("Popover.Panel");
|
|
32
|
+
Arrow = none;
|
|
33
|
+
constructor(...args) {
|
|
34
|
+
super(...args, () => {
|
|
35
|
+
this.get(({ open }) => {
|
|
36
|
+
if (open) return this.listen();
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
get triggerProps() {
|
|
41
|
+
const { id, open } = this;
|
|
42
|
+
return {
|
|
43
|
+
ref: this.anchor,
|
|
44
|
+
type: "button",
|
|
45
|
+
id,
|
|
46
|
+
"aria-haspopup": "dialog",
|
|
47
|
+
"aria-expanded": open,
|
|
48
|
+
"aria-controls": open ? `${id}-panel` : void 0,
|
|
49
|
+
onClick: this.toggle
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
/** For the panel: hidden at the origin until measured, then at `place`. */
|
|
53
|
+
get panelProps() {
|
|
54
|
+
const { id, place } = this;
|
|
55
|
+
return {
|
|
56
|
+
ref: this.mount,
|
|
57
|
+
id: `${id}-panel`,
|
|
58
|
+
role: "dialog",
|
|
59
|
+
"aria-labelledby": id,
|
|
60
|
+
tabIndex: -1,
|
|
61
|
+
style: place ? {
|
|
62
|
+
left: `${place.left}px`,
|
|
63
|
+
top: `${place.top}px`
|
|
64
|
+
} : {
|
|
65
|
+
left: "0px",
|
|
66
|
+
top: "0px",
|
|
67
|
+
opacity: "0"
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
/** For the arrow, once placed, with `above` for styling. */
|
|
72
|
+
get arrowProps() {
|
|
73
|
+
const { arrow, side } = this.place || {
|
|
74
|
+
arrow: 0,
|
|
75
|
+
side: "below"
|
|
76
|
+
};
|
|
77
|
+
return {
|
|
78
|
+
above: side == "above",
|
|
79
|
+
props: {
|
|
80
|
+
"aria-hidden": "true",
|
|
81
|
+
style: { left: `${arrow - 5}px` }
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
toggle() {
|
|
86
|
+
this.change(!this.open);
|
|
87
|
+
}
|
|
88
|
+
/** Close, returning focus to the button when `restore`. */
|
|
89
|
+
close(restore) {
|
|
90
|
+
if (!this.open) return;
|
|
91
|
+
this.change(false);
|
|
92
|
+
if (restore) TRIGGER.get(this)?.focus();
|
|
93
|
+
}
|
|
94
|
+
change(open) {
|
|
95
|
+
this.open = open;
|
|
96
|
+
this.place = void 0;
|
|
97
|
+
if (this.onToggle) this.onToggle(open);
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Close on a press outside, Escape or focus leaving, and follow scroll and resize, while open; returns the release.
|
|
101
|
+
* Defaults to `document` and `window`.
|
|
102
|
+
*/
|
|
103
|
+
listen() {
|
|
104
|
+
const inside = (target) => target instanceof Node && [TRIGGER.get(this), PANEL.get(this)].some((element) => element?.contains(target));
|
|
105
|
+
const press = (event) => {
|
|
106
|
+
if (!inside(event.target)) this.close();
|
|
107
|
+
};
|
|
108
|
+
const key = (event) => {
|
|
109
|
+
if (event.key == "Escape") {
|
|
110
|
+
event.preventDefault();
|
|
111
|
+
event.stopPropagation();
|
|
112
|
+
this.close(true);
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
const leave = (event) => {
|
|
116
|
+
if (event.relatedTarget && !inside(event.relatedTarget)) this.close();
|
|
117
|
+
};
|
|
118
|
+
const move = () => this.reposition();
|
|
119
|
+
document.addEventListener("pointerdown", press, true);
|
|
120
|
+
document.addEventListener("keydown", key, true);
|
|
121
|
+
document.addEventListener("focusout", leave);
|
|
122
|
+
window.addEventListener("resize", move);
|
|
123
|
+
window.addEventListener("scroll", move, true);
|
|
124
|
+
return () => {
|
|
125
|
+
document.removeEventListener("pointerdown", press, true);
|
|
126
|
+
document.removeEventListener("keydown", key, true);
|
|
127
|
+
document.removeEventListener("focusout", leave);
|
|
128
|
+
window.removeEventListener("resize", move);
|
|
129
|
+
window.removeEventListener("scroll", move, true);
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
/** Where the panel fits beside the button, or `undefined` before both mount. Defaults to the DOM's layout. */
|
|
133
|
+
measure() {
|
|
134
|
+
const trigger = TRIGGER.get(this);
|
|
135
|
+
const panel = PANEL.get(this);
|
|
136
|
+
if (!trigger || !panel) return;
|
|
137
|
+
return place({
|
|
138
|
+
anchor: trigger.getBoundingClientRect(),
|
|
139
|
+
width: panel.offsetWidth,
|
|
140
|
+
height: panel.offsetHeight,
|
|
141
|
+
view: {
|
|
142
|
+
left: 0,
|
|
143
|
+
top: 0,
|
|
144
|
+
width: window.innerWidth,
|
|
145
|
+
height: window.innerHeight
|
|
146
|
+
},
|
|
147
|
+
side: this.side
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
/** Re-measure and move the panel when its place changed; for a `listen` that follows layout. */
|
|
151
|
+
reposition() {
|
|
152
|
+
const next = this.measure();
|
|
153
|
+
if (!next) return;
|
|
154
|
+
const { place: last } = this;
|
|
155
|
+
if (!last || last.left != next.left || last.top != next.top || last.side != next.side || last.arrow != next.arrow) this.place = next;
|
|
156
|
+
}
|
|
157
|
+
/** Ref for the element the panel anchors to, in `triggerProps`. */
|
|
158
|
+
anchor(element) {
|
|
159
|
+
if (element) TRIGGER.set(this, element);
|
|
160
|
+
}
|
|
161
|
+
/** Ref for the panel, in `panelProps`. */
|
|
162
|
+
mount(element) {
|
|
163
|
+
if (!element) return;
|
|
164
|
+
PANEL.set(this, element);
|
|
165
|
+
this.reposition();
|
|
166
|
+
(element.querySelector("[autofocus]") || element).focus();
|
|
167
|
+
}
|
|
168
|
+
render(props) {
|
|
169
|
+
const { open, place } = this;
|
|
170
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(this.Trigger, {}), open && /* @__PURE__ */ jsx(this.Layer, { children: /* @__PURE__ */ jsxs(this.Panel, { children: [place && /* @__PURE__ */ jsx(this.Arrow, {}), props.children] }) })] });
|
|
171
|
+
}
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
//#endregion
|
|
175
|
+
export { Popover };
|
|
176
|
+
//# sourceMappingURL=base.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"base.js","names":[],"sources":["../../src/popover/base.tsx"],"sourcesContent":["import { Component, type Model } from '@expressive/mvc';\n\nimport { missing, none } from '../piece';\nimport { place, type Place, type Side } from './place';\n\ndeclare namespace Popover {\n export { Place, Side };\n\n export type Wrap = (props: { children: Component.Node }) => Component.Node;\n export type Part = () => Component.Node;\n}\n\nconst TRIGGER = new WeakMap<Popover, HTMLElement>();\n\nconst PANEL = new WeakMap<Popover, HTMLElement>();\n\nlet ID = 0;\n\nconst Layer = ({ children }: { children: Component.Node }): Component.Node => children;\n\n/**\n * Anchored, non-modal panel behavior without a look: toggling, placement, dismissal and ARIA, assembled from pieces.\n *\n * Every visible piece is a placeholder that throws when rendered - a skin assigns each one it renders. `Layer` renders\n * in place by default; a skin replaces it to move the panel out of the page. `Arrow` renders nothing unless assigned.\n * Pieces spread the matching `*Props` getter onto their element to stay accessible and placed.\n */\nclass Popover extends Component {\n /** Content of the button. */\n label: Component.Node = 'Open';\n\n open = false;\n onToggle?: (open: boolean) => void = undefined;\n\n /** Preferred side of the button. */\n side: Popover.Side = 'below';\n\n /** The button's id; prefixes the panel's. */\n id = `popover-${++ID}`;\n\n /** Where the panel sits, once measured. */\n place?: Popover.Place = undefined;\n\n Trigger: Popover.Part = missing('Popover.Trigger');\n Layer: Popover.Wrap = Layer;\n Panel: Popover.Wrap = missing('Popover.Panel');\n Arrow: Popover.Part = none;\n\n constructor(...args: Model.Args) {\n super(...args, () => {\n this.get(({ open }) => {\n if (open) return this.listen();\n });\n });\n }\n\n get triggerProps() {\n const { id, open } = this;\n\n return {\n ref: this.anchor,\n type: 'button',\n id,\n 'aria-haspopup': 'dialog',\n 'aria-expanded': open,\n 'aria-controls': open ? `${id}-panel` : undefined,\n onClick: this.toggle\n } as const;\n }\n\n /** For the panel: hidden at the origin until measured, then at `place`. */\n get panelProps() {\n const { id, place } = this;\n\n return {\n ref: this.mount,\n id: `${id}-panel`,\n role: 'dialog',\n 'aria-labelledby': id,\n tabIndex: -1,\n style: place ? { left: `${place.left}px`, top: `${place.top}px` } : { left: '0px', top: '0px', opacity: '0' }\n } as const;\n }\n\n /** For the arrow, once placed, with `above` for styling. */\n get arrowProps() {\n const { arrow, side } = this.place || { arrow: 0, side: 'below' };\n\n return {\n above: side == 'above',\n props: { 'aria-hidden': 'true', style: { left: `${arrow - 5}px` } }\n } as const;\n }\n\n toggle() {\n this.change(!this.open);\n }\n\n /** Close, returning focus to the button when `restore`. */\n close(restore?: boolean) {\n if (!this.open) return;\n\n this.change(false);\n\n if (restore) TRIGGER.get(this)?.focus();\n }\n\n private change(open: boolean) {\n this.open = open;\n this.place = undefined;\n\n if (this.onToggle) this.onToggle(open);\n }\n\n /**\n * Close on a press outside, Escape or focus leaving, and follow scroll and resize, while open; returns the release.\n * Defaults to `document` and `window`.\n */\n protected listen(): () => void {\n const inside = (target: EventTarget | null) =>\n target instanceof Node && [TRIGGER.get(this), PANEL.get(this)].some((element) => element?.contains(target));\n\n const press = (event: Event) => {\n if (!inside(event.target)) this.close();\n };\n\n const key = (event: KeyboardEvent) => {\n if (event.key == 'Escape') {\n event.preventDefault();\n event.stopPropagation();\n this.close(true);\n }\n };\n\n const leave = (event: FocusEvent) => {\n if (event.relatedTarget && !inside(event.relatedTarget)) this.close();\n };\n\n const move = () => this.reposition();\n\n document.addEventListener('pointerdown', press, true);\n document.addEventListener('keydown', key, true);\n document.addEventListener('focusout', leave);\n window.addEventListener('resize', move);\n window.addEventListener('scroll', move, true);\n\n return () => {\n document.removeEventListener('pointerdown', press, true);\n document.removeEventListener('keydown', key, true);\n document.removeEventListener('focusout', leave);\n window.removeEventListener('resize', move);\n window.removeEventListener('scroll', move, true);\n };\n }\n\n /** Where the panel fits beside the button, or `undefined` before both mount. Defaults to the DOM's layout. */\n protected measure(): Popover.Place | undefined {\n const trigger = TRIGGER.get(this);\n const panel = PANEL.get(this);\n\n if (!trigger || !panel) return;\n\n return place({\n anchor: trigger.getBoundingClientRect(),\n width: panel.offsetWidth,\n height: panel.offsetHeight,\n view: { left: 0, top: 0, width: window.innerWidth, height: window.innerHeight },\n side: this.side\n });\n }\n\n /** Re-measure and move the panel when its place changed; for a `listen` that follows layout. */\n protected reposition() {\n const next = this.measure();\n\n if (!next) return;\n\n const { place: last } = this;\n\n if (!last || last.left != next.left || last.top != next.top || last.side != next.side || last.arrow != next.arrow)\n this.place = next;\n }\n\n /** Ref for the element the panel anchors to, in `triggerProps`. */\n anchor(element: HTMLElement | null) {\n if (element) TRIGGER.set(this, element);\n }\n\n /** Ref for the panel, in `panelProps`. */\n mount(element: HTMLElement | null) {\n if (!element) return;\n\n PANEL.set(this, element);\n this.reposition();\n (element.querySelector<HTMLElement>('[autofocus]') || element).focus();\n }\n\n render(props: { children?: Component.Node }): Component.Node {\n const { open, place } = this;\n\n return (\n <>\n <this.Trigger />\n {open && (\n <this.Layer>\n <this.Panel>\n {place && <this.Arrow />}\n {props.children}\n </this.Panel>\n </this.Layer>\n )}\n </>\n );\n }\n}\n\nexport { Popover };\n"],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
//#region src/popover/place.d.ts
|
|
2
|
+
type Side = 'below' | 'above';
|
|
3
|
+
interface Place {
|
|
4
|
+
left: number;
|
|
5
|
+
top: number;
|
|
6
|
+
side: Side;
|
|
7
|
+
/** Arrow center from the box's left edge. */
|
|
8
|
+
arrow: number;
|
|
9
|
+
}
|
|
10
|
+
//#endregion
|
|
11
|
+
export { Place, Side };
|
|
12
|
+
//# sourceMappingURL=place.d.ts.map
|