@pathscale/ui 2.7.3 → 2.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,36 @@
1
+ /*
2
+ * FlexGrid: a list that starts short and grows.
3
+ *
4
+ * Deliberately almost bare. The rows are the consumer's, and the one thing this
5
+ * component owns visually is the scroll container that `onScroll` reads: it has
6
+ * to actually scroll, or the reveal never triggers.
7
+ */
8
+
9
+ .flex-grid {
10
+ display: flex;
11
+ min-height: 0;
12
+ flex-direction: column;
13
+ }
14
+
15
+ /*
16
+ * `overflow-y: auto` is load-bearing, not decoration. `onScroll` measures
17
+ * `scrollTop` against `scrollHeight` on this element, and an element that does
18
+ * not scroll reports the two as equal forever, so nothing is ever revealed.
19
+ */
20
+ .flex-grid__list {
21
+ display: flex;
22
+ min-height: 0;
23
+ flex: 1;
24
+ flex-direction: column;
25
+ overflow-y: auto;
26
+ overscroll-behavior: contain;
27
+ }
28
+
29
+ .flex-grid__more {
30
+ flex: none;
31
+ align-self: center;
32
+ }
33
+
34
+ .flex-grid__status {
35
+ flex: none;
36
+ }
@@ -0,0 +1,36 @@
1
+ import type { Component as __LayoutComponent } from "solid-js";
2
+ import type { JSX } from "@solidjs/web";
3
+ import "./FlexGrid.css";
4
+ interface FlexGridProps<T> extends Omit<JSX.HTMLAttributes<HTMLDivElement>, "children"> {
5
+ class?: string;
6
+ /** Everything there is, in the order it should appear. */
7
+ rows: readonly T[];
8
+ /** One row. */
9
+ children: (row: T, index: () => number) => JSX.Element;
10
+ /** How many rows to build up front, and how many each reveal adds. */
11
+ pageSize?: number;
12
+ /**
13
+ * Reveal from the end rather than the start: a log or a transcript, whose
14
+ * useful rows are the last ones and whose "more" means *earlier*.
15
+ */
16
+ fromEnd?: boolean;
17
+ /**
18
+ * Reveal more as the reader scrolls, without them having to ask. On by
19
+ * default, because it is what a list of this shape is expected to do.
20
+ */
21
+ autoLoad?: boolean;
22
+ /**
23
+ * The control that reveals the next page, given the count so it can say how
24
+ * many are coming. Omit it to rely on scrolling alone.
25
+ */
26
+ more?: (props: {
27
+ count: number;
28
+ remaining: number;
29
+ reveal: () => void;
30
+ }) => JSX.Element;
31
+ /** Shown in place of the list when there is nothing at all. */
32
+ empty?: JSX.Element;
33
+ }
34
+ declare const FlexGridRoot: __LayoutComponent<FlexGridProps<unknown>>;
35
+ export { FlexGridRoot };
36
+ export type { FlexGridProps };
@@ -0,0 +1,91 @@
1
+ import { createComponent, insert, memo, mergeProps, spread, template } from "@solidjs/web";
2
+ import { defineComponent } from "solid-layouts/solid-2/application-boundary";
3
+ import { For, Show, omit } from "solid-js";
4
+ import { twMerge } from "../../lib/twMerge.js";
5
+ import { CLASSES, componentRecipe } from "./FlexGrid.recipe.js";
6
+ import "./FlexGrid.css";
7
+ import { createFlexGrid } from "./createFlexGrid.js";
8
+ var _tmpl$ = /*#__PURE__*/ template("<div>"), _tmpl$2 = /*#__PURE__*/ template("<div><!><!><!>");
9
+ const __solidLayoutFlexGridRoot = (_stable, p)=>{
10
+ const others = omit(p, "class", "rows", "children", "pageSize", "fromEnd", "autoLoad", "more", "empty");
11
+ const grid = createFlexGrid({
12
+ rows: ()=>p.rows,
13
+ pageSize: p.pageSize,
14
+ fromEnd: p.fromEnd
15
+ });
16
+ const autoLoad = ()=>p.autoLoad ?? true;
17
+ const moreControl = ()=>p.more?.({
18
+ count: grid.nextCount(),
19
+ remaining: grid.remaining(),
20
+ reveal: grid.revealMore
21
+ });
22
+ var _el$ = _tmpl$();
23
+ spread(_el$, mergeProps(others, ()=>({
24
+ class: twMerge(CLASSES.base, p.class)
25
+ }), {
26
+ "data-slot": "root"
27
+ }), true);
28
+ insert(_el$, createComponent(Show, {
29
+ get when () {
30
+ return p.rows.length > 0;
31
+ },
32
+ get fallback () {
33
+ return p.empty;
34
+ },
35
+ get children () {
36
+ var _el$2 = _tmpl$2(), _el$5 = _el$2.firstChild, _el$6 = _el$5.nextSibling, _el$7 = _el$6.nextSibling;
37
+ spread(_el$2, mergeProps(()=>({
38
+ class: CLASSES.slot.list
39
+ }), {
40
+ "data-slot": "flex-grid-list",
41
+ onScroll: (event)=>{
42
+ if (autoLoad()) grid.onScroll(event);
43
+ }
44
+ }), true);
45
+ insert(_el$2, createComponent(Show, {
46
+ get when () {
47
+ return memo(()=>!!(p.fromEnd && grid.hasMore()))() ? p.more : p.fromEnd && grid.hasMore();
48
+ },
49
+ get children () {
50
+ var _el$3 = _tmpl$();
51
+ spread(_el$3, mergeProps(()=>({
52
+ class: CLASSES.slot.more
53
+ }), {
54
+ "data-slot": "flex-grid-more"
55
+ }), true);
56
+ insert(_el$3, moreControl);
57
+ return _el$3;
58
+ }
59
+ }), _el$5);
60
+ insert(_el$2, createComponent(For, {
61
+ get each () {
62
+ return grid.visible();
63
+ },
64
+ children: (row, index)=>p.children(row, index)
65
+ }), _el$6);
66
+ insert(_el$2, createComponent(Show, {
67
+ get when () {
68
+ return memo(()=>!!(!p.fromEnd && grid.hasMore()))() ? p.more : !p.fromEnd && grid.hasMore();
69
+ },
70
+ get children () {
71
+ var _el$4 = _tmpl$();
72
+ spread(_el$4, mergeProps(()=>({
73
+ class: CLASSES.slot.more
74
+ }), {
75
+ "data-slot": "flex-grid-more"
76
+ }), true);
77
+ insert(_el$4, moreControl);
78
+ return _el$4;
79
+ }
80
+ }), _el$7);
81
+ return _el$2;
82
+ }
83
+ }));
84
+ return _el$;
85
+ };
86
+ const FlexGridRoot = defineComponent({
87
+ recipe: componentRecipe,
88
+ layout: __solidLayoutFlexGridRoot,
89
+ embedded: true
90
+ });
91
+ export { FlexGridRoot };
@@ -0,0 +1,15 @@
1
+ export declare const CLASSES: {
2
+ readonly base: "flex-grid";
3
+ readonly slot: {
4
+ readonly list: "flex-grid__list";
5
+ readonly more: "flex-grid__more";
6
+ };
7
+ };
8
+ export declare const componentRecipe: import("solid-layouts/solid-2").Recipe<{
9
+ readonly component: "flex-grid";
10
+ readonly slots: {
11
+ readonly root: {};
12
+ readonly "flex-grid-list": {};
13
+ readonly "flex-grid-more": {};
14
+ };
15
+ }>;
@@ -0,0 +1,39 @@
1
+ import { recipe } from "../../lib/layouts/index.js";
2
+ const CLASSES = {
3
+ base: "flex-grid",
4
+ slot: {
5
+ list: "flex-grid__list",
6
+ more: "flex-grid__more"
7
+ }
8
+ };
9
+ const componentRecipe = recipe({
10
+ component: "flex-grid",
11
+ slots: {
12
+ root: {},
13
+ "flex-grid-list": {},
14
+ "flex-grid-more": {}
15
+ },
16
+ _layouts: {
17
+ slots: {
18
+ root: {
19
+ base: "",
20
+ axes: {}
21
+ },
22
+ "flex-grid-list": {
23
+ base: "",
24
+ axes: {}
25
+ },
26
+ "flex-grid-more": {
27
+ base: "",
28
+ axes: {}
29
+ }
30
+ },
31
+ stateKeys: [],
32
+ slotIds: {
33
+ root: 2,
34
+ "flex-grid-list": 0,
35
+ "flex-grid-more": 1
36
+ }
37
+ }
38
+ });
39
+ export { CLASSES, componentRecipe };
@@ -0,0 +1,69 @@
1
+ import { type Accessor } from "solid-js";
2
+ /**
3
+ * Incremental reveal for a long list: render N, reveal more on demand.
4
+ *
5
+ * This is not `createDataGrid`. That one is discrete pagination — page 3 of 12,
6
+ * with the reader moving between fixed windows. This is the other shape: one
7
+ * continuous list that starts short and grows, which is what a feed, a log or an
8
+ * item list wants. Both exist because neither can be spelled as the other
9
+ * without lying to the reader about where they are.
10
+ *
11
+ * The cost this removes is construction, not data. A list of 700 rows where 20
12
+ * are visible still builds 700 subtrees, and in a component library each of
13
+ * those rows is a handful of instances rather than one element. The consuming
14
+ * application measured a 40-row log at 122-178ms of a 203-339ms panel build for
15
+ * exactly this reason.
16
+ *
17
+ * Headless on purpose: the reveal rule is the part worth testing and the part
18
+ * every consumer shares, while the markup is the part each one wants to own.
19
+ */
20
+ export interface CreateFlexGridOptions<T> {
21
+ /** Everything there is, in the order it should appear. */
22
+ rows: Accessor<readonly T[]>;
23
+ /**
24
+ * How many rows to build up front, and how many each reveal adds.
25
+ *
26
+ * Twenty by default, which covers a typical viewport without building a
27
+ * screenful of rows nobody scrolls to.
28
+ */
29
+ pageSize?: number;
30
+ /**
31
+ * Reveal from the end rather than the start.
32
+ *
33
+ * A task log or a transcript is read newest-first: its useful rows are the
34
+ * last ones, so the first page should be the tail and "more" should mean
35
+ * *earlier*. A to-do list is the opposite. Defaults to `false`.
36
+ */
37
+ fromEnd?: boolean;
38
+ }
39
+ export interface FlexGridModel<T> {
40
+ /** The rows to render now. */
41
+ visible: Accessor<T[]>;
42
+ /** How many rows exist in total, revealed or not. */
43
+ total: Accessor<number>;
44
+ /** How many are still hidden. */
45
+ remaining: Accessor<number>;
46
+ /** Whether anything is left to reveal. */
47
+ hasMore: Accessor<boolean>;
48
+ /** How many the next reveal will add: the page size, or whatever is left. */
49
+ nextCount: Accessor<number>;
50
+ /** Reveal one more page. A no-op once everything is visible. */
51
+ revealMore(): void;
52
+ /** Reveal everything at once. */
53
+ revealAll(): void;
54
+ /** Back to a single page. */
55
+ reset(): void;
56
+ /**
57
+ * Handle a scroll event from the element that owns the scrollbar.
58
+ *
59
+ * A scroll listener rather than an `IntersectionObserver` sentinel, and that
60
+ * is deliberate rather than dated: consumers of this library do not all run in
61
+ * a browser engine that has observers. One of them renders through Blitz,
62
+ * where `IntersectionObserver`, `ResizeObserver` and `MutationObserver` are
63
+ * all absent, so a sentinel would never intersect, `revealMore` would never
64
+ * fire, and the list would silently stop at its first page with no error to
65
+ * explain it. A `scroll` event is the one signal every target has.
66
+ */
67
+ onScroll(event: Event): void;
68
+ }
69
+ export declare function createFlexGrid<T>(options: CreateFlexGridOptions<T>): FlexGridModel<T>;
@@ -0,0 +1,44 @@
1
+ import { createMemo, createSignal } from "solid-js";
2
+ const SCROLL_THRESHOLD = 120;
3
+ function createFlexGrid(options) {
4
+ const pageSize = Math.max(1, options.pageSize ?? 20);
5
+ const [limit, setLimit] = createSignal(pageSize);
6
+ const total = createMemo(()=>options.rows().length);
7
+ const visible = createMemo(()=>{
8
+ const all = options.rows();
9
+ const count = Math.min(limit(), all.length);
10
+ return options.fromEnd ? all.slice(all.length - count) : all.slice(0, count);
11
+ });
12
+ const remaining = createMemo(()=>Math.max(0, total() - visible().length));
13
+ const hasMore = createMemo(()=>remaining() > 0);
14
+ const nextCount = createMemo(()=>Math.min(pageSize, remaining()));
15
+ const revealMore = ()=>{
16
+ if (!hasMore()) return;
17
+ setLimit((current)=>current + pageSize);
18
+ };
19
+ const revealAll = ()=>{
20
+ setLimit(total());
21
+ };
22
+ const reset = ()=>{
23
+ setLimit(pageSize);
24
+ };
25
+ const onScroll = (event)=>{
26
+ if (!hasMore()) return;
27
+ const element = event.currentTarget;
28
+ if (!element) return;
29
+ const atEdge = options.fromEnd ? element.scrollTop <= SCROLL_THRESHOLD : element.scrollHeight - element.scrollTop - element.clientHeight <= SCROLL_THRESHOLD;
30
+ if (atEdge) revealMore();
31
+ };
32
+ return {
33
+ visible,
34
+ total,
35
+ remaining,
36
+ hasMore,
37
+ nextCount,
38
+ revealMore,
39
+ revealAll,
40
+ reset,
41
+ onScroll
42
+ };
43
+ }
44
+ export { createFlexGrid };
@@ -0,0 +1,9 @@
1
+ import { FlexGridRoot } from "./FlexGrid.generated";
2
+ declare const FlexGrid: import("solid-js").Component<import("./FlexGrid.generated").FlexGridProps<unknown>> & {
3
+ Root: import("solid-js").Component<import("./FlexGrid.generated").FlexGridProps<unknown>>;
4
+ };
5
+ export default FlexGrid;
6
+ export { FlexGrid, FlexGridRoot };
7
+ export type { FlexGridProps } from "./FlexGrid.generated";
8
+ export { createFlexGrid } from "./createFlexGrid";
9
+ export type { CreateFlexGridOptions, FlexGridModel } from "./createFlexGrid";
@@ -0,0 +1,8 @@
1
+ import { FlexGridRoot } from "./FlexGrid.generated.js";
2
+ const FlexGrid = Object.assign(FlexGridRoot, {
3
+ Root: FlexGridRoot
4
+ });
5
+ const flex_grid = FlexGrid;
6
+ export { createFlexGrid } from "./createFlexGrid.js";
7
+ export default flex_grid;
8
+ export { FlexGrid, FlexGridRoot };
package/dist/index.d.ts CHANGED
@@ -44,6 +44,8 @@ export { Empty, type EmptyProps } from "./components/empty";
44
44
  export type { FieldGroupProps, FieldsetActionsProps, FieldsetLegendProps, FieldsetProps, FieldsetRootProps, } from "./components/fieldset";
45
45
  export { default as Fieldset, FieldGroup, FieldsetActions, FieldsetLegend, } from "./components/fieldset";
46
46
  export { default as Flex } from "./components/flex";
47
+ export type { CreateFlexGridOptions, FlexGridModel, FlexGridProps } from "./components/flex-grid";
48
+ export { createFlexGrid, default as FlexGrid } from "./components/flex-grid";
47
49
  export type { FooterProps, FooterTitleProps } from "./components/footer";
48
50
  export { default as Footer } from "./components/footer";
49
51
  export type { FieldErrorMessageProps, FormFieldProps, FormProps, FormRootProps, FormSubmitButtonProps, FormWithContextProps, } from "./components/form";
package/dist/index.js CHANGED
@@ -29,6 +29,7 @@ export { default as Dropdown } from "./components/dropdown/index.js";
29
29
  export { Empty } from "./components/empty/index.js";
30
30
  export { FieldGroup, FieldsetActions, FieldsetLegend, default as Fieldset } from "./components/fieldset/index.js";
31
31
  export { default as Flex } from "./components/flex/index.js";
32
+ export { createFlexGrid, default as FlexGrid } from "./components/flex-grid/index.js";
32
33
  export { default as Footer } from "./components/footer/index.js";
33
34
  export { FormField, FormSubmitButton, FormWithContext, default as Form } from "./components/form/index.js";
34
35
  export { GlowCard } from "./components/glow-card/index.js";
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "format": "solid-layouts-library-v2",
3
3
  "package": "@pathscale/ui",
4
- "version": "2.7.3",
4
+ "version": "2.8.0",
5
5
  "components": {
6
6
  "Accordion": {
7
7
  "kind": "embedded"
@@ -11410,6 +11410,34 @@
11410
11410
  "referenced": []
11411
11411
  }
11412
11412
  },
11413
+ "FlexGrid": {
11414
+ "key": "FlexGrid",
11415
+ "component": "FlexGrid",
11416
+ "classes": {
11417
+ "always": [],
11418
+ "byProp": {
11419
+ "slot": {
11420
+ "list": [],
11421
+ "more": []
11422
+ }
11423
+ }
11424
+ },
11425
+ "attributeSelectors": [],
11426
+ "selectors": [
11427
+ ".flex-grid",
11428
+ ".flex-grid__list",
11429
+ ".flex-grid__more",
11430
+ ".flex-grid__status"
11431
+ ],
11432
+ "cssVars": {
11433
+ "declared": [],
11434
+ "referenced": []
11435
+ },
11436
+ "keyframes": {
11437
+ "declared": [],
11438
+ "referenced": []
11439
+ }
11440
+ },
11413
11441
  "Drawer.Root": {
11414
11442
  "key": "Drawer.Root",
11415
11443
  "component": "Drawer",
@@ -24172,6 +24200,30 @@
24172
24200
  "Fieldset.Root"
24173
24201
  ]
24174
24202
  },
24203
+ {
24204
+ "selector": ".flex-grid",
24205
+ "components": [
24206
+ "FlexGrid"
24207
+ ]
24208
+ },
24209
+ {
24210
+ "selector": ".flex-grid__list",
24211
+ "components": [
24212
+ "FlexGrid"
24213
+ ]
24214
+ },
24215
+ {
24216
+ "selector": ".flex-grid__more",
24217
+ "components": [
24218
+ "FlexGrid"
24219
+ ]
24220
+ },
24221
+ {
24222
+ "selector": ".flex-grid__status",
24223
+ "components": [
24224
+ "FlexGrid"
24225
+ ]
24226
+ },
24175
24227
  {
24176
24228
  "selector": ".flex-layout",
24177
24229
  "components": [
@@ -61,7 +61,7 @@ function resolveGlassTokens(tuning, mode) {
61
61
  const { rs, rv, ds, light } = v;
62
62
  const zero = 0 === v.r;
63
63
  const controlTint = v.controlTint;
64
- const background = light ? zero ? 0 : 18 + 30 * rv : 7 * rv;
64
+ const background = light ? zero ? 0 : 18 + 30 * rv : zero ? 0 : 4 + 22 * rv;
65
65
  const border = light ? zero ? 0 : 18 + 18 * rs : 26 * rs;
66
66
  const highlight = light ? zero ? 0 : 22 + 26 * rs : 24 * rs;
67
67
  const bottomHighlight = light ? zero ? 0 : 10 + 14 * rs + 5 * ds : 3.5 * rs + 3 * rs * ds;
@@ -69,11 +69,11 @@ function resolveGlassTokens(tuning, mode) {
69
69
  const rimStart = light ? zero ? 0 : 14 + 18 * rs + 4 * ds : 17 * rs + 3 * ds;
70
70
  const rimEnd = light ? zero ? 0 : 18 + 22 * rs + 4 * ds : 23 * rs + 5 * ds;
71
71
  const innerGlow = light ? 0.035 * rs + 0.045 * rs * ds + 0.012 * ds : 0.055 * rs * ds + 0.018 * ds;
72
- const topGlow = light ? 8 * ds * (0.4 + 0.6 * rs) : 6 * ds * (0.35 + 0.65 * rs);
73
- const bottomGlow = light ? 9 * ds * (0.25 + 0.65 * rs) : 5 * ds * (0.2 + 0.55 * rs);
74
- const sheen = light ? 14 * ds * (0.25 + 0.75 * rs) : 10 * ds * (0.25 + 0.75 * rs);
72
+ const topGlow = light ? 8 * ds * (0.4 + 0.6 * rs) : 13 * ds * (0.35 + 0.65 * rs);
73
+ const bottomGlow = light ? 9 * ds * (0.25 + 0.65 * rs) : 10 * ds * (0.2 + 0.55 * rs);
74
+ const sheen = light ? 14 * ds * (0.25 + 0.75 * rs) : 20 * ds * (0.25 + 0.75 * rs);
75
75
  const fallbackBackground = Math.min(70, light ? background + 10 * rs : 8 * background + 12 * rs);
76
- const shadow = ds > 0 ? `0 ${px(light ? 5 + 10 * ds : 4 + 10 * ds)} ${px(light ? 20 + 28 * ds : 18 + 26 * ds)} rgb(0 0 0 / ${pct(light ? 2 + 5 * ds : 3 + 7 * ds)})` : "0 0 0 rgb(0 0 0 / 0%)";
76
+ const shadow = ds > 0 ? `0 ${px(light ? 5 + 10 * ds : 4 + 10 * ds)} ${px(light ? 20 + 28 * ds : 18 + 26 * ds)} rgb(0 0 0 / ${pct(light ? 2 + 5 * ds : 8 + 22 * ds)})` : "0 0 0 rgb(0 0 0 / 0%)";
77
77
  return {
78
78
  "--glass-blur": px(v.blur, 0),
79
79
  "--glass-saturation": round(1 + 0.18 * rs),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pathscale/ui",
3
- "version": "2.7.3",
3
+ "version": "2.8.0",
4
4
  "author": "pathscale",
5
5
  "repository": {
6
6
  "type": "git",