foldkit-mixins-data-grid 0.2.14 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/dist/fillDrag.d.ts +30 -0
  2. package/dist/fillDrag.d.ts.map +1 -0
  3. package/dist/fillDrag.js +178 -0
  4. package/dist/fillDrag.js.map +1 -0
  5. package/dist/headerDrag.d.ts +34 -0
  6. package/dist/headerDrag.d.ts.map +1 -0
  7. package/dist/headerDrag.js +112 -0
  8. package/dist/headerDrag.js.map +1 -0
  9. package/dist/holdFocus.d.ts +11 -0
  10. package/dist/holdFocus.d.ts.map +1 -0
  11. package/dist/holdFocus.js +31 -0
  12. package/dist/holdFocus.js.map +1 -0
  13. package/dist/index.d.ts +6 -0
  14. package/dist/index.js +6 -0
  15. package/dist/keepFocus.d.ts +9 -0
  16. package/dist/keepFocus.d.ts.map +1 -0
  17. package/dist/keepFocus.js +20 -0
  18. package/dist/keepFocus.js.map +1 -0
  19. package/dist/marks.d.ts +69 -0
  20. package/dist/marks.d.ts.map +1 -0
  21. package/dist/marks.js +90 -0
  22. package/dist/marks.js.map +1 -0
  23. package/dist/moreOnScroll.d.ts +12 -0
  24. package/dist/moreOnScroll.d.ts.map +1 -0
  25. package/dist/moreOnScroll.js +17 -0
  26. package/dist/moreOnScroll.js.map +1 -0
  27. package/dist/nearEnd.d.ts +15 -0
  28. package/dist/nearEnd.d.ts.map +1 -0
  29. package/dist/nearEnd.js +33 -0
  30. package/dist/nearEnd.js.map +1 -0
  31. package/dist/press.d.ts +31 -0
  32. package/dist/press.d.ts.map +1 -0
  33. package/dist/press.js +53 -0
  34. package/dist/press.js.map +1 -0
  35. package/dist/slots.d.ts +85 -0
  36. package/dist/slots.d.ts.map +1 -0
  37. package/dist/slots.js +289 -0
  38. package/dist/slots.js.map +1 -0
  39. package/dist/style.d.ts +555 -0
  40. package/dist/style.d.ts.map +1 -0
  41. package/dist/style.js +224 -0
  42. package/dist/style.js.map +1 -0
  43. package/dist/view.d.ts +140 -0
  44. package/dist/view.d.ts.map +1 -0
  45. package/dist/{index.mjs → view.js} +10 -995
  46. package/dist/view.js.map +1 -0
  47. package/package.json +12 -12
  48. package/dist/index.d.mts +0 -321
  49. package/dist/index.d.mts.map +0 -1
  50. package/dist/index.mjs.map +0 -1
package/dist/marks.js ADDED
@@ -0,0 +1,90 @@
1
+ import { GridSlots } from "./slots.js";
2
+ import { ref } from "./style.js";
3
+ import { Attr, Layers, Slot, SlotView, Slots, Style } from "foldkit-mixins";
4
+ // src/marks.ts
5
+ /**
6
+ * The marks an editable grid over a server shows on a cell, by name, styled
7
+ * on the theme's tokens, with a legend drawn from the same rules: an edit not
8
+ * sent, one saved that the read rows do not show yet, one the server refused,
9
+ * one another device's later edit replaced, and another device's focus.
10
+ * `CellMark.name` stays any string; these are the shared ones.
11
+ */
12
+ const GridMarks = [
13
+ "pending",
14
+ "saved",
15
+ "refused",
16
+ "replaced",
17
+ "peer"
18
+ ];
19
+ /** A dot in the top end corner, filled or hollow: an edit on its way. */
20
+ const dot = (color, hollow) => ({ backgroundImage: hollow ? `radial-gradient(circle at calc(100% - 6px) 6px, transparent 2px, ${color} 2.5px, ${color} 3.5px, transparent 4px)` : `radial-gradient(circle at calc(100% - 6px) 6px, ${color} 3px, transparent 3.5px)` });
21
+ /** A 2px inset edge, and a ground: something that needs reading. */
22
+ const edge = (color, ground) => ({
23
+ backgroundImage: "none",
24
+ boxShadow: `inset 0 0 0 ${ref.border.thick} ${color}`,
25
+ ...ground === void 0 ? {} : { background: ground }
26
+ });
27
+ const looks = {
28
+ pending: dot(ref.info.default, false),
29
+ saved: dot(ref.success.ink, true),
30
+ refused: edge(ref.error.ink, ref.error.subtle),
31
+ replaced: edge(ref.warning.ink),
32
+ peer: edge(`var(--fk-grid-peer, ${ref.secondary.default})`, ref.secondary.subtle)
33
+ };
34
+ /** Each mark's rule on an element carrying `data-mark`, and an outline where colour is forced. */
35
+ const marked = Style.compose(...GridMarks.map((mark) => Style.nest(`&[data-mark="${mark}"]`, looks[mark])), Style.at("@media (forced-colors: active)", Style.nest("&[data-mark]", { outline: `${ref.border.thick} solid CanvasText` })));
36
+ const GridLegendSlots = Slots.define({
37
+ /** The list: labelled, so a screen reader names what the marks are. */
38
+ legend: Slot.make({ attributes: [Attr.AriaLabel] }),
39
+ /** One mark and its words. */
40
+ legendItem: Slot.make({}),
41
+ /** The mark drawn small, as the cells draw it (`data-mark`), hidden from screen readers. */
42
+ legendSwatch: Slot.make({})
43
+ });
44
+ const words = {
45
+ label: "What the marks on a cell mean",
46
+ marks: {
47
+ pending: "Not sent yet",
48
+ saved: "Saved, not yet in the table",
49
+ refused: "Not saved",
50
+ replaced: "Replaced by another device",
51
+ peer: "Another device is here"
52
+ }
53
+ };
54
+ /**
55
+ * A legend of the marks, each beside a swatch drawn by the same rules as the
56
+ * cells (`GridMarkStyle` styles both), so the two cannot drift apart.
57
+ */
58
+ const GridLegend = () => SlotView.forMessages().define(GridLegendSlots, (input, slots, h) => {
59
+ const said = input.words ?? words;
60
+ return h.ul(slots.legend.attrs([h.AriaLabel(said.label)]), (input.marks ?? GridMarks).map((mark) => h.li(slots.legendItem.attrs([]), [h.span(slots.legendSwatch.attrs([h.DataAttribute("mark", mark), h.AriaHidden(true)]), []), said.marks[mark]])));
61
+ });
62
+ /** The marks on the grid's cells. Attach after `GridStyle`, whose plain dot it replaces by name. */
63
+ const GridMarkStyle = Style.forSlots(GridSlots)({ cell: marked }, { layer: Layers.standard.layer("components") });
64
+ /** The legend's look, its swatches drawn by the cells' own rules. */
65
+ const GridLegendStyle = Style.forSlots(GridLegendSlots)({
66
+ legend: {
67
+ display: "flex",
68
+ flexWrap: "wrap",
69
+ gap: `${ref.space["2xs"]} ${ref.space.md}`,
70
+ listStyle: "none",
71
+ margin: "0",
72
+ padding: "0",
73
+ fontSize: ref.size.xs,
74
+ color: ref.text.muted
75
+ },
76
+ legendItem: {
77
+ display: "inline-flex",
78
+ alignItems: "center",
79
+ gap: ref.space.xs
80
+ },
81
+ legendSwatch: Style.compose(Style.inline({
82
+ display: "inline-block",
83
+ width: "12px",
84
+ height: "12px",
85
+ borderRadius: ref.radius.xs
86
+ }), marked)
87
+ }, { layer: Layers.standard.layer("components") });
88
+ export { GridLegend, GridLegendSlots, GridLegendStyle, GridMarkStyle, GridMarks };
89
+
90
+ //# sourceMappingURL=marks.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"marks.js","names":[],"sources":["../src/marks.ts"],"sourcesContent":["/**\n * The marks an editable grid over a server shows on a cell, by name, styled\n * on the theme's tokens, with a legend drawn from the same rules: an edit not\n * sent, one saved that the read rows do not show yet, one the server refused,\n * one another device's later edit replaced, and another device's focus.\n * `CellMark.name` stays any string; these are the shared ones.\n */\nimport { Attr, Layers, Slot, Slots, SlotView, Style } from 'foldkit-mixins'\nimport type { Html } from 'foldkit/html'\nimport { GridSlots } from './slots.js'\nimport { ref } from './style.js'\n\nexport const GridMarks = ['pending', 'saved', 'refused', 'replaced', 'peer'] as const\nexport type GridMark = (typeof GridMarks)[number]\n\n/** A dot in the top end corner, filled or hollow: an edit on its way. */\nconst dot = (color: string, hollow: boolean) => ({\n backgroundImage: hollow\n ? `radial-gradient(circle at calc(100% - 6px) 6px, transparent 2px, ${color} 2.5px, ${color} 3.5px, transparent 4px)`\n : `radial-gradient(circle at calc(100% - 6px) 6px, ${color} 3px, transparent 3.5px)`,\n})\n/** A 2px inset edge, and a ground: something that needs reading. */\nconst edge = (color: string, ground?: string) => ({\n backgroundImage: 'none',\n boxShadow: `inset 0 0 0 ${ref.border.thick} ${color}`,\n ...(ground === undefined ? {} : { background: ground }),\n})\n\n// Edges use the families' ink, which keeps 3:1 against the base surface; a\n// peer's colour can be the application's, per person, through --fk-grid-peer.\nconst looks: Readonly<Record<GridMark, Record<string, string>>> = {\n pending: dot(ref.info.default, false),\n saved: dot(ref.success.ink, true),\n refused: edge(ref.error.ink, ref.error.subtle),\n replaced: edge(ref.warning.ink),\n peer: edge(`var(--fk-grid-peer, ${ref.secondary.default})`, ref.secondary.subtle),\n}\n\n/** Each mark's rule on an element carrying `data-mark`, and an outline where colour is forced. */\nconst marked = Style.compose(\n ...GridMarks.map(mark => Style.nest(`&[data-mark=\"${mark}\"]`, looks[mark])),\n // Forced colours drop box shadows and backgrounds: an outline still says it.\n Style.at(\n '@media (forced-colors: active)',\n Style.nest('&[data-mark]', { outline: `${ref.border.thick} solid CanvasText` }),\n ),\n)\n\nexport const GridLegendSlots = Slots.define({\n /** The list: labelled, so a screen reader names what the marks are. */\n legend: Slot.make({ attributes: [Attr.AriaLabel] }),\n /** One mark and its words. */\n legendItem: Slot.make({}),\n /** The mark drawn small, as the cells draw it (`data-mark`), hidden from screen readers. */\n legendSwatch: Slot.make({}),\n})\n\n/** The words for each mark, and the legend's own label. */\nexport interface GridLegendWords {\n readonly label: string\n readonly marks: Readonly<Record<GridMark, string>>\n}\n\nconst words: GridLegendWords = {\n label: 'What the marks on a cell mean',\n marks: {\n pending: 'Not sent yet',\n saved: 'Saved, not yet in the table',\n refused: 'Not saved',\n replaced: 'Replaced by another device',\n peer: 'Another device is here',\n },\n}\n\nexport interface GridLegendInput {\n /** The marks to explain, in order; all five when omitted. */\n readonly marks?: ReadonlyArray<GridMark>\n readonly words?: GridLegendWords\n}\n\n/**\n * A legend of the marks, each beside a swatch drawn by the same rules as the\n * cells (`GridMarkStyle` styles both), so the two cannot drift apart.\n */\nexport const GridLegend = <Message>() =>\n SlotView.forMessages<Message>().define(\n GridLegendSlots,\n (input: GridLegendInput, slots, h): Html => {\n const said = input.words ?? words\n return h.ul(\n slots.legend.attrs([h.AriaLabel(said.label)]),\n (input.marks ?? GridMarks).map(mark =>\n h.li(slots.legendItem.attrs([]), [\n h.span(\n slots.legendSwatch.attrs([h.DataAttribute('mark', mark), h.AriaHidden(true)]),\n [],\n ),\n said.marks[mark],\n ]),\n ),\n )\n },\n )\n\n/** The marks on the grid's cells. Attach after `GridStyle`, whose plain dot it replaces by name. */\nexport const GridMarkStyle = Style.forSlots(GridSlots)(\n { cell: marked },\n { layer: Layers.standard.layer('components') },\n)\n\n/** The legend's look, its swatches drawn by the cells' own rules. */\nexport const GridLegendStyle = Style.forSlots(GridLegendSlots)(\n {\n legend: {\n display: 'flex',\n flexWrap: 'wrap',\n gap: `${ref.space['2xs']} ${ref.space.md}`,\n listStyle: 'none',\n margin: '0',\n padding: '0',\n fontSize: ref.size.xs,\n color: ref.text.muted,\n },\n legendItem: { display: 'inline-flex', alignItems: 'center', gap: ref.space.xs },\n legendSwatch: Style.compose(\n Style.inline({\n display: 'inline-block',\n width: '12px',\n height: '12px',\n borderRadius: ref.radius.xs,\n }),\n marked,\n ),\n },\n { layer: Layers.standard.layer('components') },\n)\n"],"mappings":";;;;;;;;;;;AAYA,MAAa,YAAY;CAAC;CAAW;CAAS;CAAW;CAAY;AAAM;;AAI3E,MAAM,OAAO,OAAe,YAAqB,EAC/C,iBAAiB,SACb,oEAAoE,MAAM,UAAU,MAAM,4BAC1F,mDAAmD,MAAM,0BAC/D;;AAEA,MAAM,QAAQ,OAAe,YAAqB;CAChD,iBAAiB;CACjB,WAAW,eAAe,IAAI,OAAO,MAAM,GAAG;CAC9C,GAAI,WAAW,KAAA,IAAY,CAAC,IAAI,EAAE,YAAY,OAAO;AACvD;AAIA,MAAM,QAA4D;CAChE,SAAS,IAAI,IAAI,KAAK,SAAS,KAAK;CACpC,OAAO,IAAI,IAAI,QAAQ,KAAK,IAAI;CAChC,SAAS,KAAK,IAAI,MAAM,KAAK,IAAI,MAAM,MAAM;CAC7C,UAAU,KAAK,IAAI,QAAQ,GAAG;CAC9B,MAAM,KAAK,uBAAuB,IAAI,UAAU,QAAQ,IAAI,IAAI,UAAU,MAAM;AAClF;;AAGA,MAAM,SAAS,MAAM,QACnB,GAAG,UAAU,KAAI,SAAQ,MAAM,KAAK,gBAAgB,KAAK,KAAK,MAAM,KAAK,CAAC,GAE1E,MAAM,GACJ,kCACA,MAAM,KAAK,gBAAgB,EAAE,SAAS,GAAG,IAAI,OAAO,MAAM,mBAAmB,CAAC,CAChF,CACF;AAEA,MAAa,kBAAkB,MAAM,OAAO;;CAE1C,QAAQ,KAAK,KAAK,EAAE,YAAY,CAAC,KAAK,SAAS,EAAE,CAAC;;CAElD,YAAY,KAAK,KAAK,CAAC,CAAC;;CAExB,cAAc,KAAK,KAAK,CAAC,CAAC;AAC5B,CAAC;AAQD,MAAM,QAAyB;CAC7B,OAAO;CACP,OAAO;EACL,SAAS;EACT,OAAO;EACP,SAAS;EACT,UAAU;EACV,MAAM;CACR;AACF;;;;;AAYA,MAAa,mBACX,SAAS,YAAqB,CAAC,CAAC,OAC9B,kBACC,OAAwB,OAAO,MAAY;CAC1C,MAAM,OAAO,MAAM,SAAS;CAC5B,OAAO,EAAE,GACP,MAAM,OAAO,MAAM,CAAC,EAAE,UAAU,KAAK,KAAK,CAAC,CAAC,IAC3C,MAAM,SAAS,UAAA,CAAW,KAAI,SAC7B,EAAE,GAAG,MAAM,WAAW,MAAM,CAAC,CAAC,GAAG,CAC/B,EAAE,KACA,MAAM,aAAa,MAAM,CAAC,EAAE,cAAc,QAAQ,IAAI,GAAG,EAAE,WAAW,IAAI,CAAC,CAAC,GAC5E,CAAC,CACH,GACA,KAAK,MAAM,KACb,CAAC,CACH,CACF;AACF,CACF;;AAGF,MAAa,gBAAgB,MAAM,SAAS,SAAS,CAAC,CACpD,EAAE,MAAM,OAAO,GACf,EAAE,OAAO,OAAO,SAAS,MAAM,YAAY,EAAE,CAC/C;;AAGA,MAAa,kBAAkB,MAAM,SAAS,eAAe,CAAC,CAC5D;CACE,QAAQ;EACN,SAAS;EACT,UAAU;EACV,KAAK,GAAG,IAAI,MAAM,OAAO,GAAG,IAAI,MAAM;EACtC,WAAW;EACX,QAAQ;EACR,SAAS;EACT,UAAU,IAAI,KAAK;EACnB,OAAO,IAAI,KAAK;CAClB;CACA,YAAY;EAAE,SAAS;EAAe,YAAY;EAAU,KAAK,IAAI,MAAM;CAAG;CAC9E,cAAc,MAAM,QAClB,MAAM,OAAO;EACX,SAAS;EACT,OAAO;EACP,QAAQ;EACR,cAAc,IAAI,OAAO;CAC3B,CAAC,GACD,MACF;AACF,GACA,EAAE,OAAO,OAAO,SAAS,MAAM,YAAY,EAAE,CAC/C"}
@@ -0,0 +1,12 @@
1
+ import { GridSlots } from "./slots.js";
2
+ import { GridInput } from "./view.js";
3
+ import { SlotView } from "foldkit-mixins";
4
+ /**
5
+ * Sends the grid's `onMore` as its More button comes within 200px of the
6
+ * grid's visible box, and again after each load while the button stays
7
+ * there; the button stays for the keyboard. Nothing is asked while the rows'
8
+ * source is busy. Attach it to a grid's view:
9
+ * `DataGridView<Message>().define(Grid).pipe(MoreOnScroll)`.
10
+ */
11
+ export declare const MoreOnScroll: <Row, Id extends string, GridMessage, Message>(view: SlotView.SlotView<typeof GridSlots, GridInput<Row, Id, GridMessage, Message>, Message>) => SlotView.SlotView<typeof GridSlots, GridInput<Row, Id, GridMessage, Message>, Message>;
12
+ //# sourceMappingURL=moreOnScroll.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"moreOnScroll.d.ts","names":[],"sources":["../src/moreOnScroll.ts"],"mappings":";;;;;;;;;;qBAaa,eAAgB,KAAK,mBAAmB,aAAa,SAAO,MACjE,SAAS,gBAAgB,WAAW,UAAU,KAAK,IAAI,aAAa,UAAU,aACnF,SAAS,gBAAgB,WAAW,UAAU,KAAK,IAAI,aAAa,UAAU"}
@@ -0,0 +1,17 @@
1
+ import * as Mount from "foldkit/mount";
2
+ import { GridSlots } from "./slots.js";
3
+ import { isBusy } from "./view.js";
4
+ import { Behavior } from "foldkit-mixins";
5
+ import { Nearing } from "./nearEnd.js";
6
+ // src/moreOnScroll.ts
7
+ /**
8
+ * Sends the grid's `onMore` as its More button comes within 200px of the
9
+ * grid's visible box, and again after each load while the button stays
10
+ * there; the button stays for the keyboard. Nothing is asked while the rows'
11
+ * source is busy. Attach it to a grid's view:
12
+ * `DataGridView<Message>().define(Grid).pipe(MoreOnScroll)`.
13
+ */
14
+ const MoreOnScroll = (view) => Behavior.attach(Behavior.forSlots(GridSlots)({ more: Behavior.slot({ mount: (input) => Mount.mapMessage(Nearing({ watching: !isBusy(input.status) }), () => input.onMore) }) }, { name: "MoreOnScroll" }))(view);
15
+ export { MoreOnScroll };
16
+
17
+ //# sourceMappingURL=moreOnScroll.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"moreOnScroll.js","names":[],"sources":["../src/moreOnScroll.ts"],"sourcesContent":["import { Behavior, type SlotView } from 'foldkit-mixins'\nimport * as Mount from 'foldkit/mount'\nimport { Nearing } from './nearEnd.js'\nimport { GridSlots } from './slots.js'\nimport { isBusy, type GridInput } from './view.js'\n\n/**\n * Sends the grid's `onMore` as its More button comes within 200px of the\n * grid's visible box, and again after each load while the button stays\n * there; the button stays for the keyboard. Nothing is asked while the rows'\n * source is busy. Attach it to a grid's view:\n * `DataGridView<Message>().define(Grid).pipe(MoreOnScroll)`.\n */\nexport const MoreOnScroll = <Row, Id extends string, GridMessage, Message>(\n view: SlotView.SlotView<typeof GridSlots, GridInput<Row, Id, GridMessage, Message>, Message>,\n): SlotView.SlotView<typeof GridSlots, GridInput<Row, Id, GridMessage, Message>, Message> =>\n Behavior.attach(\n Behavior.forSlots(GridSlots)<GridInput<Row, Id, GridMessage, Message>, Message>(\n {\n more: Behavior.slot({\n // The view keys the button by the rows loaded and whether a load is\n // in flight, so this starts afresh after each load and, still in\n // view, asks again. It is drawn only when there is an `onMore`.\n mount: input =>\n Mount.mapMessage(Nearing({ watching: !isBusy(input.status) }), () => input.onMore!),\n }),\n },\n { name: 'MoreOnScroll' },\n ),\n )(view)\n"],"mappings":";;;;;;;;;;;;;AAaA,MAAa,gBACX,SAEA,SAAS,OACP,SAAS,SAAS,SAAS,CAAC,CAC1B,EACE,MAAM,SAAS,KAAK,EAIlB,QAAO,UACL,MAAM,WAAW,QAAQ,EAAE,UAAU,CAAC,OAAO,MAAM,MAAM,EAAE,CAAC,SAAS,MAAM,MAAO,EACtF,CAAC,EACH,GACA,EAAE,MAAM,eAAe,CACzB,CACF,CAAC,CAAC,IAAI"}
@@ -0,0 +1,15 @@
1
+ import { Schema } from "effect";
2
+ import * as Mount from "foldkit/mount";
3
+ export declare const NearEnd: Schema.TaggedStruct<"NearEnd", {}>;
4
+ /**
5
+ * The element coming within 200px of the grid's visible box: one report each
6
+ * time it comes in, measured against the scroll container it sits in, not
7
+ * the page. Not `watching` (a load in flight), or no observer (SSR, an old
8
+ * browser), it reports nothing.
9
+ */
10
+ export declare const Nearing: Mount.MountDefinitionWithArgs<"DataGridNearEnd", {
11
+ watching: Schema.Boolean;
12
+ }, {
13
+ readonly _tag: "NearEnd";
14
+ }>;
15
+ //# sourceMappingURL=nearEnd.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nearEnd.d.ts","names":[],"sources":["../src/nearEnd.ts"],"mappings":";;qBAGa,SAAO,OAAA;;;;;;;qBAgBP,SAAO,MAAA"}
@@ -0,0 +1,33 @@
1
+ import { Effect, Queue, Schema, Stream } from "effect";
2
+ import * as Mount from "foldkit/mount";
3
+ // src/nearEnd.ts
4
+ const NearEnd = Schema.TaggedStruct("NearEnd", {});
5
+ /** How far ahead of the end the next rows are asked for, so they arrive before they are needed. */
6
+ const ahead = "200px";
7
+ /**
8
+ * The element coming within 200px of the grid's visible box: one report each
9
+ * time it comes in, measured against the scroll container it sits in, not
10
+ * the page. Not `watching` (a load in flight), or no observer (SSR, an old
11
+ * browser), it reports nothing.
12
+ */
13
+ const Nearing = Mount.defineStream("DataGridNearEnd", {
14
+ args: { watching: Schema.Boolean },
15
+ messages: [NearEnd],
16
+ execute: ({ element, watching }) => Stream.callback((queue) => Effect.gen(function* () {
17
+ const Observer = globalThis.IntersectionObserver;
18
+ if (!watching || Observer === void 0) return;
19
+ yield* Effect.acquireRelease(Effect.sync(() => {
20
+ const observer = new Observer((entries) => {
21
+ if (entries.some((entry) => entry.isIntersecting)) Queue.offerUnsafe(queue, NearEnd.make({}));
22
+ }, {
23
+ root: element.closest("[role=\"grid\"]"),
24
+ rootMargin: ahead
25
+ });
26
+ observer.observe(element);
27
+ return observer;
28
+ }), (observer) => Effect.sync(() => observer.disconnect()));
29
+ }))
30
+ });
31
+ export { NearEnd, Nearing };
32
+
33
+ //# sourceMappingURL=nearEnd.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nearEnd.js","names":[],"sources":["../src/nearEnd.ts"],"sourcesContent":["import { Effect, Queue, Schema, Stream } from 'effect'\nimport * as Mount from 'foldkit/mount'\n\nexport const NearEnd = Schema.TaggedStruct('NearEnd', {})\n\ntype ObserverCtor = new (\n callback: IntersectionObserverCallback,\n options?: IntersectionObserverInit,\n) => IntersectionObserver\n\n/** How far ahead of the end the next rows are asked for, so they arrive before they are needed. */\nconst ahead = '200px'\n\n/**\n * The element coming within 200px of the grid's visible box: one report each\n * time it comes in, measured against the scroll container it sits in, not\n * the page. Not `watching` (a load in flight), or no observer (SSR, an old\n * browser), it reports nothing.\n */\nexport const Nearing = Mount.defineStream('DataGridNearEnd', {\n args: { watching: Schema.Boolean },\n messages: [NearEnd],\n execute: ({ element, watching }) =>\n Stream.callback<typeof NearEnd.Type>(queue =>\n Effect.gen(function* () {\n const Observer = (globalThis as { IntersectionObserver?: ObserverCtor })\n .IntersectionObserver\n if (!watching || Observer === undefined) return\n yield* Effect.acquireRelease(\n Effect.sync(() => {\n const observer = new Observer(\n entries => {\n if (entries.some(entry => entry.isIntersecting)) {\n Queue.offerUnsafe(queue, NearEnd.make({}))\n }\n },\n { root: element.closest('[role=\"grid\"]'), rootMargin: ahead },\n )\n observer.observe(element)\n return observer\n }),\n observer => Effect.sync(() => observer.disconnect()),\n )\n }),\n ),\n})\n"],"mappings":";;;AAGA,MAAa,UAAU,OAAO,aAAa,WAAW,CAAC,CAAC;;AAQxD,MAAM,QAAQ;;;;;;;AAQd,MAAa,UAAU,MAAM,aAAa,mBAAmB;CAC3D,MAAM,EAAE,UAAU,OAAO,QAAQ;CACjC,UAAU,CAAC,OAAO;CAClB,UAAU,EAAE,SAAS,eACnB,OAAO,UAA8B,UACnC,OAAO,IAAI,aAAa;EACtB,MAAM,WAAY,WACf;EACH,IAAI,CAAC,YAAY,aAAa,KAAA,GAAW;EACzC,OAAO,OAAO,eACZ,OAAO,WAAW;GAChB,MAAM,WAAW,IAAI,UACnB,YAAW;IACT,IAAI,QAAQ,MAAK,UAAS,MAAM,cAAc,GAC5C,MAAM,YAAY,OAAO,QAAQ,KAAK,CAAC,CAAC,CAAC;GAE7C,GACA;IAAE,MAAM,QAAQ,QAAQ,iBAAe;IAAG,YAAY;GAAM,CAC9D;GACA,SAAS,QAAQ,OAAO;GACxB,OAAO;EACT,CAAC,IACD,aAAY,OAAO,WAAW,SAAS,WAAW,CAAC,CACrD;CACF,CAAC,CACH;AACJ,CAAC"}
@@ -0,0 +1,31 @@
1
+ import { Schema } from "effect";
2
+ import * as Mount from "foldkit/mount";
3
+ export declare const CellClicked: Schema.TaggedStruct<"CellClicked", {
4
+ /** The clicked gridcell's DOM id. */
5
+ readonly cell: Schema.String;
6
+ readonly shiftKey: Schema.Boolean;
7
+ /** Ctrl, or Meta on a Mac: a toggle. */
8
+ readonly toggleKey: Schema.Boolean;
9
+ }>;
10
+ /**
11
+ * Clicks on a grid's cells, with their modifier keys, and the fill handle's
12
+ * drag (`fillDragOn`), from listeners on the element that holds the cells
13
+ * rather than one per cell: one Mount, since an element holds one. A click
14
+ * outside a gridcell, on the fill handle, or ending a fill reports nothing;
15
+ * what a click means is the grid's update.
16
+ */
17
+ export declare const CellPress: Mount.MountDefinitionNoArgs<"DataGridCellPress", {
18
+ readonly _tag: "CellClicked";
19
+ readonly cell: string;
20
+ readonly shiftKey: boolean;
21
+ readonly toggleKey: boolean;
22
+ } | {
23
+ readonly _tag: "FillDragStarted";
24
+ } | {
25
+ readonly _tag: "FillDraggedOver";
26
+ readonly cell: string;
27
+ } | {
28
+ readonly _tag: "FillDragEnded";
29
+ readonly completed: boolean;
30
+ }>;
31
+ //# sourceMappingURL=press.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"press.d.ts","names":[],"sources":["../src/press.ts"],"mappings":";;qBAUa,aAAW,OAAA;;;;;;;;;;;;;;qBAqBX,WAAS,MAAA"}
package/dist/press.js ADDED
@@ -0,0 +1,53 @@
1
+ import { Effect, Queue, Schema, Stream } from "effect";
2
+ import * as Mount from "foldkit/mount";
3
+ import { FillDragEnded, FillDragStarted, FillDraggedOver, fillDragOn } from "./fillDrag.js";
4
+ // src/press.ts
5
+ const CellClicked = Schema.TaggedStruct("CellClicked", {
6
+ /** The clicked gridcell's DOM id. */
7
+ cell: Schema.String,
8
+ shiftKey: Schema.Boolean,
9
+ /** Ctrl, or Meta on a Mac: a toggle. */
10
+ toggleKey: Schema.Boolean
11
+ });
12
+ /**
13
+ * Clicks on a grid's cells, with their modifier keys, and the fill handle's
14
+ * drag (`fillDragOn`), from listeners on the element that holds the cells
15
+ * rather than one per cell: one Mount, since an element holds one. A click
16
+ * outside a gridcell, on the fill handle, or ending a fill reports nothing;
17
+ * what a click means is the grid's update.
18
+ */
19
+ const CellPress = Mount.defineStream("DataGridCellPress", {
20
+ messages: [
21
+ CellClicked,
22
+ FillDragStarted,
23
+ FillDraggedOver,
24
+ FillDragEnded
25
+ ],
26
+ execute: ({ element }) => Stream.callback((queue) => Effect.acquireRelease(Effect.sync(() => {
27
+ const fill = fillDragOn(element, (fact) => Queue.offerUnsafe(queue, fact));
28
+ const listener = (event) => {
29
+ if (fill.owns(event)) return;
30
+ const from = event.target;
31
+ if (!(from instanceof Element)) return;
32
+ const cell = from.closest("[role=\"gridcell\"]");
33
+ if (cell === null || !element.contains(cell) || cell.id === "") return;
34
+ const modified = event;
35
+ Queue.offerUnsafe(queue, CellClicked.make({
36
+ cell: cell.id,
37
+ shiftKey: modified.shiftKey === true,
38
+ toggleKey: modified.ctrlKey === true || modified.metaKey === true
39
+ }));
40
+ };
41
+ element.addEventListener("click", listener);
42
+ return {
43
+ listener,
44
+ fill
45
+ };
46
+ }), ({ listener, fill }) => Effect.sync(() => {
47
+ element.removeEventListener("click", listener);
48
+ fill.dispose();
49
+ })))
50
+ });
51
+ export { CellClicked, CellPress };
52
+
53
+ //# sourceMappingURL=press.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"press.js","names":[],"sources":["../src/press.ts"],"sourcesContent":["import { Effect, Queue, Schema, Stream } from 'effect'\nimport * as Mount from 'foldkit/mount'\nimport {\n FillDragEnded,\n FillDragStarted,\n FillDraggedOver,\n type FillFact,\n fillDragOn,\n} from './fillDrag.js'\n\nexport const CellClicked = Schema.TaggedStruct('CellClicked', {\n /** The clicked gridcell's DOM id. */\n cell: Schema.String,\n shiftKey: Schema.Boolean,\n /** Ctrl, or Meta on a Mac: a toggle. */\n toggleKey: Schema.Boolean,\n})\n\ntype Modified = Event & {\n readonly shiftKey?: boolean\n readonly ctrlKey?: boolean\n readonly metaKey?: boolean\n}\n\n/**\n * Clicks on a grid's cells, with their modifier keys, and the fill handle's\n * drag (`fillDragOn`), from listeners on the element that holds the cells\n * rather than one per cell: one Mount, since an element holds one. A click\n * outside a gridcell, on the fill handle, or ending a fill reports nothing;\n * what a click means is the grid's update.\n */\nexport const CellPress = Mount.defineStream('DataGridCellPress', {\n messages: [CellClicked, FillDragStarted, FillDraggedOver, FillDragEnded],\n execute: ({ element }) =>\n Stream.callback<typeof CellClicked.Type | FillFact>(queue =>\n Effect.acquireRelease(\n Effect.sync(() => {\n const fill = fillDragOn(element, fact => Queue.offerUnsafe(queue, fact))\n const listener = (event: Event) => {\n if (fill.owns(event)) return\n const from = event.target\n if (!(from instanceof Element)) return\n const cell = from.closest('[role=\"gridcell\"]')\n if (cell === null || !element.contains(cell) || cell.id === '') return\n const modified = event as Modified\n Queue.offerUnsafe(\n queue,\n CellClicked.make({\n cell: cell.id,\n shiftKey: modified.shiftKey === true,\n toggleKey: modified.ctrlKey === true || modified.metaKey === true,\n }),\n )\n }\n element.addEventListener('click', listener)\n return { listener, fill }\n }),\n ({ listener, fill }) =>\n Effect.sync(() => {\n element.removeEventListener('click', listener)\n fill.dispose()\n }),\n ),\n ),\n})\n"],"mappings":";;;;AAUA,MAAa,cAAc,OAAO,aAAa,eAAe;;CAE5D,MAAM,OAAO;CACb,UAAU,OAAO;;CAEjB,WAAW,OAAO;AACpB,CAAC;;;;;;;;AAeD,MAAa,YAAY,MAAM,aAAa,qBAAqB;CAC/D,UAAU;EAAC;EAAa;EAAiB;EAAiB;CAAa;CACvE,UAAU,EAAE,cACV,OAAO,UAA6C,UAClD,OAAO,eACL,OAAO,WAAW;EAChB,MAAM,OAAO,WAAW,UAAS,SAAQ,MAAM,YAAY,OAAO,IAAI,CAAC;EACvE,MAAM,YAAY,UAAiB;GACjC,IAAI,KAAK,KAAK,KAAK,GAAG;GACtB,MAAM,OAAO,MAAM;GACnB,IAAI,EAAE,gBAAgB,UAAU;GAChC,MAAM,OAAO,KAAK,QAAQ,qBAAmB;GAC7C,IAAI,SAAS,QAAQ,CAAC,QAAQ,SAAS,IAAI,KAAK,KAAK,OAAO,IAAI;GAChE,MAAM,WAAW;GACjB,MAAM,YACJ,OACA,YAAY,KAAK;IACf,MAAM,KAAK;IACX,UAAU,SAAS,aAAa;IAChC,WAAW,SAAS,YAAY,QAAQ,SAAS,YAAY;GAC/D,CAAC,CACH;EACF;EACA,QAAQ,iBAAiB,SAAS,QAAQ;EAC1C,OAAO;GAAE;GAAU;EAAK;CAC1B,CAAC,IACA,EAAE,UAAU,WACX,OAAO,WAAW;EAChB,QAAQ,oBAAoB,SAAS,QAAQ;EAC7C,KAAK,QAAQ;CACf,CAAC,CACL,CACF;AACJ,CAAC"}
@@ -0,0 +1,85 @@
1
+ import { Attr, Capability, Event, Slot, Slots } from "foldkit-mixins";
2
+ export declare const GridSlots: Slots.Contract<{
3
+ /** The scroll container: `role="grid"`, the one tab stop, and the keyboard. */
4
+ readonly root: Slot.UnnamedSlot<Capability.Capability<"Focusable", readonly [Capability.Capability<"Interactive", readonly [Capability.Capability<"Base", readonly []>]>]>, readonly [Event.EventToken<"keydown">], readonly [Attr.AttrToken<"role">, Attr.AttrToken<"aria-label">, Attr.AttrToken<"aria-rowcount">, Attr.AttrToken<"aria-colcount">, Attr.AttrToken<"aria-activedescendant">], readonly Slot.RequirementName[], boolean>;
5
+ /** The header row's group; it stays at the top while the body scrolls. */
6
+ readonly header: Slot.UnnamedSlot<Capability.Capability<"Container", readonly [Capability.Capability<"Interactive", readonly [Capability.Capability<"Base", readonly []>]>]>, readonly Slot.EventName[], readonly Slot.AttributeName[], readonly Slot.RequirementName[], boolean>;
7
+ readonly headerRow: Slot.UnnamedSlot<Capability.Capability<"Container", readonly [Capability.Capability<"Interactive", readonly [Capability.Capability<"Base", readonly []>]>]>, readonly Slot.EventName[], readonly Slot.AttributeName[], readonly Slot.RequirementName[], boolean>;
8
+ /**
9
+ * One per drawn column. `data-pinned` is `start` or `end` on a pinned
10
+ * column; while a header is dragged it has `data-dragging` and follows the
11
+ * pointer by a `transform`, and the shown neighbour it would land beside
12
+ * has `data-drop`, `before` or `after`.
13
+ */
14
+ readonly headerCell: Slot.UnnamedSlot<Capability.Capability<"Base", readonly []>, readonly Slot.EventName[], readonly Slot.AttributeName[], readonly Slot.RequirementName[], boolean>;
15
+ /**
16
+ * A column's resize handle at its end edge: a `role="separator"` with its
17
+ * width as `aria-valuenow`, dragged with the pointer, or stepped with the
18
+ * arrow keys once focused. It is out of the tab order, so the grid stays
19
+ * one tab stop. Only resizable columns have one.
20
+ */
21
+ readonly resizeHandle: Slot.UnnamedSlot<Capability.Capability<"Focusable", readonly [Capability.Capability<"Interactive", readonly [Capability.Capability<"Base", readonly []>]>]>, readonly [Event.EventToken<"keydown">], readonly Slot.AttributeName[], readonly Slot.RequirementName[], boolean>;
22
+ readonly body: Slot.UnnamedSlot<Capability.Capability<"Container", readonly [Capability.Capability<"Interactive", readonly [Capability.Capability<"Base", readonly []>]>]>, readonly Slot.EventName[], readonly Slot.AttributeName[], readonly Slot.RequirementName[], boolean>;
23
+ /** One per drawn row; `aria-rowindex` is its place among every row, drawn or not. */
24
+ readonly row: Slot.UnnamedSlot<Capability.Capability<"Container", readonly [Capability.Capability<"Interactive", readonly [Capability.Capability<"Base", readonly []>]>]>, readonly Slot.EventName[], readonly Slot.AttributeName[], readonly Slot.RequirementName[], boolean>;
25
+ /** One per drawn cell; `data-focused` marks the current one, `data-pinned` a pinned one. */
26
+ readonly cell: Slot.UnnamedSlot<Capability.Capability<"Base", readonly []>, readonly [Event.EventToken<"mousedown">], readonly Slot.AttributeName[], readonly Slot.RequirementName[], boolean>;
27
+ /** A row counted but not loaded yet, drawn at its height for a skeleton to style. */
28
+ readonly placeholder: Slot.UnnamedSlot<Capability.Capability<"Base", readonly []>, readonly Slot.EventName[], readonly Slot.AttributeName[], readonly Slot.RequirementName[], boolean>;
29
+ /**
30
+ * The text field a cell is edited in, drawn in place of its content while
31
+ * the edit lasts; `aria-invalid` when the column refused the draft.
32
+ */
33
+ readonly editor: Slot.UnnamedSlot<Capability.Capability<"TextInput", readonly [Capability.Capability<"Focusable", readonly [Capability.Capability<"Interactive", readonly [Capability.Capability<"Base", readonly []>]>]>]>, readonly [Event.EventToken<"input">, Event.EventToken<"keydown">, Event.EventToken<"blur">], readonly Slot.AttributeName[], readonly Slot.RequirementName[], boolean>;
34
+ /**
35
+ * Why the column refused a draft, shown below the field while the edit
36
+ * lasts; the field names it with `aria-describedby`.
37
+ */
38
+ readonly editorError: Slot.UnnamedSlot<Capability.Capability<"Base", readonly []>, readonly Slot.EventName[], readonly Slot.AttributeName[], readonly Slot.RequirementName[], boolean>;
39
+ /**
40
+ * The editor of a column whose schema's text is one of a few literals,
41
+ * drawn in place of the cell's content while the edit lasts, like
42
+ * `editor`: a `role="combobox"` showing the draft, its options in
43
+ * `choiceList` (or, with `choiceEditor: 'native'`, a `select`).
44
+ */
45
+ readonly choice: Slot.UnnamedSlot<Capability.Capability<"Focusable", readonly [Capability.Capability<"Interactive", readonly [Capability.Capability<"Base", readonly []>]>]>, readonly [Event.EventToken<"change">, Event.EventToken<"keydown">, Event.EventToken<"blur">], readonly Slot.AttributeName[], readonly Slot.RequirementName[], boolean>;
46
+ /**
47
+ * A choice's options, `role="listbox"`, below the cell, or above it when
48
+ * the cell is low in the view (`data-place="above"`).
49
+ */
50
+ readonly choiceList: Slot.UnnamedSlot<Capability.Capability<"Base", readonly []>, readonly Slot.EventName[], readonly Slot.AttributeName[], readonly Slot.RequirementName[], boolean>;
51
+ /** One literal a `choice` offers: `role="option"`, `aria-selected` on the draft. */
52
+ readonly choiceOption: Slot.UnnamedSlot<Capability.Capability<"Base", readonly []>, readonly [Event.EventToken<"click">], readonly Slot.AttributeName[], readonly Slot.RequirementName[], boolean>;
53
+ /** Said in place of rows when there are none: empty, loading, or failed. */
54
+ readonly status: Slot.UnnamedSlot<Capability.Capability<"Base", readonly []>, readonly Slot.EventName[], readonly Slot.AttributeName[], readonly Slot.RequirementName[], boolean>;
55
+ /**
56
+ * The button on a column's header that opens its menu, when the view is
57
+ * given `columnMenu`: `aria-haspopup="menu"` and `aria-expanded`, out of
58
+ * the tab order like the grid's other controls.
59
+ */
60
+ readonly menuButton: Slot.UnnamedSlot<Capability.Capability<"Interactive", readonly [Capability.Capability<"Base", readonly []>]>, readonly [Event.EventToken<"click">], readonly [Attr.AttrToken<"aria-label">], readonly Slot.RequirementName[], boolean>;
61
+ /**
62
+ * A column's open menu, below its header: a `role="menu"` that takes focus
63
+ * and points at its active item with `aria-activedescendant`, as the grid
64
+ * points at its cell. Focus leaving it closes it.
65
+ */
66
+ readonly menu: Slot.UnnamedSlot<Capability.Capability<"Focusable", readonly [Capability.Capability<"Interactive", readonly [Capability.Capability<"Base", readonly []>]>]>, readonly [Event.EventToken<"keydown">], readonly Slot.AttributeName[], readonly Slot.RequirementName[], boolean>;
67
+ /** One of a menu's items; `data-active` marks the one the keyboard is on. */
68
+ readonly menuItem: Slot.UnnamedSlot<Capability.Capability<"Base", readonly []>, readonly [Event.EventToken<"click">], readonly Slot.AttributeName[], readonly Slot.RequirementName[], boolean>;
69
+ /**
70
+ * The button on a sortable column's header: a click sends its sort Message.
71
+ * `data-sort` is `asc` or `desc` while the column is sorted.
72
+ */
73
+ readonly sort: Slot.UnnamedSlot<Capability.Capability<"Interactive", readonly [Capability.Capability<"Base", readonly []>]>, readonly [Event.EventToken<"click">], readonly Slot.AttributeName[], readonly Slot.RequirementName[], boolean>;
74
+ /**
75
+ * The square at the end corner of the range, or of the focused cell, in a
76
+ * grid that edits: dragged, it fills the cells it is drawn over. Pointer
77
+ * only and hidden from assistive technology; Ctrl+D and Ctrl+R fill by key.
78
+ */
79
+ readonly fillHandle: Slot.UnnamedSlot<Capability.Capability<"Base", readonly []>, readonly Slot.EventName[], readonly Slot.AttributeName[], readonly Slot.RequirementName[], boolean>;
80
+ /** Below the rows: a failure that left them on screen, and the More button. */
81
+ readonly footer: Slot.UnnamedSlot<Capability.Capability<"Container", readonly [Capability.Capability<"Interactive", readonly [Capability.Capability<"Base", readonly []>]>]>, readonly Slot.EventName[], readonly Slot.AttributeName[], readonly Slot.RequirementName[], boolean>;
82
+ readonly retry: Slot.UnnamedSlot<Capability.Capability<"Interactive", readonly [Capability.Capability<"Base", readonly []>]>, readonly [Event.EventToken<"click">], readonly Slot.AttributeName[], readonly Slot.RequirementName[], boolean>;
83
+ readonly more: Slot.UnnamedSlot<Capability.Capability<"Interactive", readonly [Capability.Capability<"Base", readonly []>]>, readonly [Event.EventToken<"click">], readonly Slot.AttributeName[], readonly Slot.RequirementName[], boolean>;
84
+ }>;
85
+ //# sourceMappingURL=slots.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"slots.d.ts","names":[],"sources":["../src/slots.ts"],"mappings":";qBA8Ba,WAAS,MAAA"}