@pathscale/ui 2.11.5 → 2.11.7

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,44 @@
1
+ /*
2
+ * The containing block every component that owns a non-portaled overlay needs.
3
+ *
4
+ * One definition rather than one per component, for the same reason
5
+ * `material.css` exists: Dropdown, Select, Popover, Tooltip, Dialog, Drawer and
6
+ * anything else that opens a floating layer all need exactly this, and copying
7
+ * a one-line rule into seven files is seven places to be wrong in.
8
+ *
9
+ * ## What it is for
10
+ *
11
+ * These overlays are `position: fixed` and live inside their owning component's
12
+ * subtree rather than in a portal. (Portals are what we moved *away* from: under
13
+ * Blitz a portal makes a transient semantic subtree, so an option can paint for
14
+ * one frame and vanish before the next sibling opens.) A fixed child positioned
15
+ * in viewport coordinates routinely lands far outside its parent's box: a
16
+ * 220x239 dropdown menu under a 72x28 pill trigger overhangs by ~267px.
17
+ *
18
+ * A renderer resolves a pointer by descending the box tree, and it descends into
19
+ * a child that escaped its parent's bounds only when the parent carries the
20
+ * hoisted-content area that a *stacking context* provides. `position: relative`
21
+ * alone does not create one unless it also carries a `z-index`. Without a
22
+ * stacking context the overlay paints, reports itself visible to the semantic
23
+ * tree, and silently takes no clicks: hit-testing rejects the descent before it
24
+ * ever reaches the item.
25
+ *
26
+ * That failure is invisible to any check that activates a node semantically
27
+ * rather than by pointer, which is how it shipped: the menu was addressable and
28
+ * the value changed, so the check passed while a real mouse could not click it.
29
+ *
30
+ * `isolation: isolate` exists precisely to create a stacking context with no
31
+ * other visual effect, which is why it is the property used here rather than a
32
+ * `z-index` that would also reorder painting.
33
+ *
34
+ * ## How to use it
35
+ *
36
+ * Import this file and put `ui-overlay-host` on the component's root, alongside
37
+ * whatever positioning that root already sets.
38
+ */
39
+ @layer components {
40
+ .ui-overlay-host {
41
+ position: relative;
42
+ isolation: isolate;
43
+ }
44
+ }
@@ -93,6 +93,10 @@ const readMotionState = (el)=>{
93
93
  y: Number.isFinite(values[5]) ? values[5] : 0
94
94
  };
95
95
  };
96
+ const applyMotionState = (el, state)=>{
97
+ el.style.opacity = String(state.opacity ?? 1);
98
+ el.style.transform = `translate3d(${state.x ?? 0}px, ${state.y ?? 0}px, 0) scale(${state.scale ?? 1})`;
99
+ };
96
100
  const getLiftOffset = (item, distance)=>{
97
101
  const radius = Math.sqrt(item.offsetX ** 2 + item.offsetY ** 2);
98
102
  if (!Number.isFinite(radius) || 0 === radius) return {
@@ -475,6 +479,7 @@ const __solidLayoutColorWheelFlower = (_stable, p)=>{
475
479
  children: (item, index)=>{
476
480
  let motionRef;
477
481
  let dotControl = null;
482
+ let hasPaintedFinalState = false;
478
483
  const isHovered = ()=>hoveredIndex() === index();
479
484
  const isPressed = ()=>pressedIndex() === index();
480
485
  const isPulsing = ()=>pulseState()?.index === index();
@@ -563,6 +568,11 @@ const __solidLayoutColorWheelFlower = (_stable, p)=>{
563
568
  createTrackedEffect(()=>{
564
569
  if (!motionRef) return;
565
570
  const target = dotTarget();
571
+ if (!hasPaintedFinalState) {
572
+ applyMotionState(motionRef, target);
573
+ hasPaintedFinalState = true;
574
+ return;
575
+ }
566
576
  const transition = dotTransition();
567
577
  dotControl?.stop();
568
578
  dotControl = runMotion(motionRef, readMotionState(motionRef), target, transition);
@@ -1,3 +1,5 @@
1
+ @import "../_shared/overlayHost.css";
2
+
1
3
  @layer components {
2
4
  .dropdown {
3
5
  position: relative;
@@ -93,8 +95,7 @@
93
95
  will-change: opacity, transform;
94
96
  transition:
95
97
  opacity 150ms cubic-bezier(0.22, 1, 0.36, 1),
96
- transform 150ms cubic-bezier(0.22, 1, 0.36, 1),
97
- visibility 150ms ease;
98
+ transform 150ms cubic-bezier(0.22, 1, 0.36, 1);
98
99
  }
99
100
 
100
101
  .dropdown__popover[data-open="true"] {
@@ -1,6 +1,6 @@
1
1
  import type { Component as __LayoutComponent } from "solid-js";
2
2
  import "./Dropdown.css";
3
- import { type JSX } from "@solidjs/web";
3
+ import type { JSX } from "@solidjs/web";
4
4
  import { type OverlayPlacement } from "../_shared/overlayPosition";
5
5
  type DropdownAlign = "start" | "end";
6
6
  type DropdownPlacement = OverlayPlacement;
@@ -1,4 +1,4 @@
1
- import { Portal, createComponent, insert, mergeProps, ref, spread, template } from "@solidjs/web";
1
+ import { createComponent, insert, mergeProps, ref, spread, template } from "@solidjs/web";
2
2
  import { defineComponent } from "solid-layouts/solid-2/application-boundary";
3
3
  import "./Dropdown.css";
4
4
  import { Show, createContext, createSignal, createTrackedEffect, createUniqueId, omit, onCleanup, onSettled, useContext } from "solid-js";
@@ -337,48 +337,44 @@ const __solidLayoutDropdownMenu = (_stable, p)=>{
337
337
  return ctx.open();
338
338
  },
339
339
  get children () {
340
- return createComponent(Portal, {
341
- get children () {
342
- var _el$6 = _tmpl$3(), _el$7 = _el$6.firstChild;
343
- var _ref$ = ctx.setMenuRef;
344
- "function" == typeof _ref$ || Array.isArray(_ref$) ? ref(()=>_ref$, _el$6) : ctx.setMenuRef = _el$6;
345
- spread(_el$6, mergeProps(others, {
346
- get id () {
347
- return ctx.menuId;
348
- }
349
- }, ()=>({
350
- class: twMerge(CLASSES.slot.popover, p.class)
351
- }), {
352
- get role () {
353
- return p.role ?? "menu";
354
- },
355
- "data-slot": "dropdown-popover",
356
- get ["data-open"] () {
357
- return ctx.open() ? "true" : "false";
358
- },
359
- get ["data-align"] () {
360
- return p.align ?? "start";
361
- },
362
- get ["data-placement"] () {
363
- return overlayPosition.placement();
364
- },
365
- get ["aria-hidden"] () {
366
- return ctx.open() ? "false" : "true";
367
- },
368
- get style () {
369
- return menuStyle();
370
- },
371
- onKeyDown: handleKeyDown
372
- }), true);
373
- spread(_el$7, mergeProps(()=>({
374
- class: CLASSES.slot.menu
375
- }), {
376
- "data-slot": "dropdown-menu"
377
- }), true);
378
- insert(_el$7, ()=>p.children);
379
- return _el$6;
340
+ var _el$6 = _tmpl$3(), _el$7 = _el$6.firstChild;
341
+ var _ref$ = ctx.setMenuRef;
342
+ "function" == typeof _ref$ || Array.isArray(_ref$) ? ref(()=>_ref$, _el$6) : ctx.setMenuRef = _el$6;
343
+ spread(_el$6, mergeProps(others, {
344
+ get id () {
345
+ return ctx.menuId;
380
346
  }
381
- });
347
+ }, ()=>({
348
+ class: twMerge(CLASSES.slot.popover, p.class)
349
+ }), {
350
+ get role () {
351
+ return p.role ?? "menu";
352
+ },
353
+ "data-slot": "dropdown-popover",
354
+ get ["data-open"] () {
355
+ return ctx.open() ? "true" : "false";
356
+ },
357
+ get ["data-align"] () {
358
+ return p.align ?? "start";
359
+ },
360
+ get ["data-placement"] () {
361
+ return overlayPosition.placement();
362
+ },
363
+ get ["aria-hidden"] () {
364
+ return ctx.open() ? "false" : "true";
365
+ },
366
+ get style () {
367
+ return menuStyle();
368
+ },
369
+ onKeyDown: handleKeyDown
370
+ }), true);
371
+ spread(_el$7, mergeProps(()=>({
372
+ class: CLASSES.slot.menu
373
+ }), {
374
+ "data-slot": "dropdown-menu"
375
+ }), true);
376
+ insert(_el$7, ()=>p.children);
377
+ return _el$6;
382
378
  }
383
379
  });
384
380
  };
@@ -1,5 +1,5 @@
1
1
  export declare const CLASSES: {
2
- readonly base: "dropdown";
2
+ readonly base: "dropdown ui-overlay-host";
3
3
  readonly slot: {
4
4
  readonly trigger: "dropdown__trigger";
5
5
  readonly popover: "dropdown__popover";
@@ -1,6 +1,6 @@
1
1
  import { recipe } from "../../lib/layouts/index.js";
2
2
  const CLASSES = {
3
- base: "dropdown",
3
+ base: "dropdown ui-overlay-host",
4
4
  slot: {
5
5
  trigger: "dropdown__trigger",
6
6
  popover: "dropdown__popover",
@@ -1,7 +1,7 @@
1
1
  import { addEvent, className, delegateEvents, effect, insert, mergeProps, ref, setAttribute, spread, template } from "@solidjs/web";
2
2
  import { defineComponent } from "solid-layouts/solid-2/application-boundary";
3
3
  import "./InlineEdit.css";
4
- import { createTrackedEffect, omit, onSettled } from "solid-js";
4
+ import { createEffect, createSignal, omit, onSettled } from "solid-js";
5
5
  import { twMerge } from "../../lib/twMerge.js";
6
6
  import { bindInlineEditDismissals, createInlineEditInteractions } from "./InlineEdit.interactions.js";
7
7
  import { CLASSES, componentRecipe } from "./InlineEdit.recipe.js";
@@ -10,19 +10,17 @@ const __solidLayoutInlineEdit = (_stable, p)=>{
10
10
  const others = omit(p, "class", "value", "onCommit", "label", "trigger", "children", "fullWidth", "disabled", "fieldClass", "ref");
11
11
  let root;
12
12
  let field;
13
+ const [isOpen, setIsOpen] = createSignal(false);
13
14
  const interactions = createInlineEditInteractions({
14
15
  value: ()=>p.value,
15
16
  disabled: ()=>Boolean(p.disabled),
16
17
  root: ()=>root,
17
18
  field: ()=>field,
18
- editingClass: CLASSES.flag.editing,
19
+ onOpenChange: setIsOpen,
19
20
  onCommit: (value)=>p.onCommit?.(value)
20
21
  });
21
- const rootClasses = ()=>twMerge(CLASSES.base, interactions.isOpen() && CLASSES.flag.editing, p.fullWidth && CLASSES.flag.fullWidth, p.disabled && CLASSES.flag.disabled, p.class);
22
- createTrackedEffect(()=>{
23
- p.value;
24
- interactions.syncValue();
25
- });
22
+ const rootClasses = ()=>twMerge(CLASSES.base, isOpen() && CLASSES.flag.editing, p.fullWidth && CLASSES.flag.fullWidth, p.disabled && CLASSES.flag.disabled, p.class);
23
+ createEffect(()=>p.value, (nextValue)=>interactions.syncValue(nextValue));
26
24
  onSettled(()=>bindInlineEditDismissals(document, window, interactions.outside, interactions.windowBlur));
27
25
  var _el$ = _tmpl$(), _el$2 = _el$.firstChild, _el$3 = _el$2.firstChild, _el$4 = _el$3.nextSibling, _el$5 = _el$2.nextSibling, _el$6 = _el$5.firstChild;
28
26
  ref(()=>(element)=>{
@@ -3,9 +3,9 @@ export declare function bindInlineEditDismissals(documentSource: Pick<Document,
3
3
  export type InlineEditInteractionOptions = {
4
4
  value: () => string;
5
5
  disabled: () => boolean;
6
- root: () => Pick<HTMLElement, "classList" | "contains"> | undefined;
6
+ root: () => Pick<HTMLElement, "contains"> | undefined;
7
7
  field: () => InlineEditField | undefined;
8
- editingClass: string;
8
+ onOpenChange: (open: boolean) => void;
9
9
  onCommit?: (value: string) => void | Promise<unknown>;
10
10
  };
11
11
  /**
@@ -25,6 +25,6 @@ export declare function createInlineEditInteractions(options: InlineEditInteract
25
25
  blur: () => void;
26
26
  outside: (target: EventTarget | null) => void;
27
27
  windowBlur: () => void;
28
- syncValue: () => void;
28
+ syncValue: (nextValue: string) => void;
29
29
  keyDown: (key: string, preventDefault: () => void) => void;
30
30
  };
@@ -2,10 +2,15 @@ function bindInlineEditDismissals(documentSource, windowSource, outside, windowB
2
2
  const pointerDown = (event)=>{
3
3
  outside(event.target);
4
4
  };
5
- documentSource.addEventListener("pointerdown", pointerDown);
5
+ const click = (event)=>{
6
+ outside(event.target);
7
+ };
8
+ documentSource.addEventListener("pointerdown", pointerDown, true);
9
+ documentSource.addEventListener("click", click, true);
6
10
  windowSource.addEventListener("blur", windowBlur);
7
11
  return ()=>{
8
- documentSource.removeEventListener("pointerdown", pointerDown);
12
+ documentSource.removeEventListener("pointerdown", pointerDown, true);
13
+ documentSource.removeEventListener("click", click, true);
9
14
  windowSource.removeEventListener("blur", windowBlur);
10
15
  };
11
16
  }
@@ -16,7 +21,7 @@ function createInlineEditInteractions(options) {
16
21
  let sourceValue = options.value();
17
22
  const apply = (next)=>{
18
23
  open = next;
19
- options.root()?.classList.toggle(options.editingClass, next);
24
+ options.onOpenChange(next);
20
25
  };
21
26
  const resetDraft = ()=>{
22
27
  draft = options.value();
@@ -39,6 +44,7 @@ function createInlineEditInteractions(options) {
39
44
  apply(false);
40
45
  };
41
46
  const commit = ()=>{
47
+ if (!open) return;
42
48
  const next = draft.trim();
43
49
  focused = false;
44
50
  apply(false);
@@ -66,8 +72,8 @@ function createInlineEditInteractions(options) {
66
72
  windowBlur: ()=>{
67
73
  if (open) commit();
68
74
  },
69
- syncValue: ()=>{
70
- if (open && options.value() !== sourceValue) cancel();
75
+ syncValue: (nextValue)=>{
76
+ if (open && nextValue !== sourceValue) cancel();
71
77
  },
72
78
  keyDown: (key, preventDefault)=>{
73
79
  if ("Enter" === key) {
@@ -1,3 +1,5 @@
1
+ @import "../_shared/overlayHost.css";
2
+
1
3
  @layer components {
2
4
  .ui-select {
3
5
  position: relative;
@@ -162,8 +164,7 @@
162
164
  pointer-events: none;
163
165
  transition:
164
166
  opacity 150ms cubic-bezier(0.22, 1, 0.36, 1),
165
- transform 150ms cubic-bezier(0.22, 1, 0.36, 1),
166
- visibility 150ms ease;
167
+ transform 150ms cubic-bezier(0.22, 1, 0.36, 1);
167
168
  }
168
169
 
169
170
  .ui-select__popover[data-open="true"] {
@@ -1,6 +1,6 @@
1
1
  import "./Select.css";
2
2
  import { type Component } from "solid-js";
3
- import { type JSX } from "@solidjs/web";
3
+ import type { JSX } from "@solidjs/web";
4
4
  import { type OverlayPlacement } from "../_shared/overlayPosition";
5
5
  import type { UIBaseProps, State } from "../vocabulary";
6
6
  type SelectKey = string | number;
@@ -1,4 +1,4 @@
1
- import { Portal, createComponent, insert, memo, mergeProps, ref, spread, template } from "@solidjs/web";
1
+ import { createComponent, insert, memo, mergeProps, ref, spread, template } from "@solidjs/web";
2
2
  import { defineComponent } from "solid-layouts/solid-2/application-boundary";
3
3
  import "./Select.css";
4
4
  import { createContext, createMemo, createSignal, createTrackedEffect, createUniqueId, omit, onCleanup, onSettled, useContext } from "solid-js";
@@ -502,36 +502,32 @@ const __solidLayoutSelectPopover = (_stable, p)=>{
502
502
  ...overlayStyle
503
503
  };
504
504
  };
505
- return createComponent(Portal, {
506
- get children () {
507
- var _el$19 = _tmpl$();
508
- var _ref$ = ctx?.setPopoverRef;
509
- ("function" == typeof _ref$ || Array.isArray(_ref$)) && ref(()=>_ref$, _el$19);
510
- spread(_el$19, mergeProps(others, ()=>({
511
- class: twMerge(CLASSES.slot.popover, p.class)
512
- }), {
513
- get ["data-theme"] () {
514
- return p.dataTheme;
515
- },
516
- "data-slot": "ui-select-popover",
517
- get ["data-open"] () {
518
- return ctx?.open() ? "true" : "false";
519
- },
520
- get ["data-placement"] () {
521
- return overlayPosition.placement();
522
- },
523
- get style () {
524
- return popoverStyle();
525
- },
526
- onPointerDown: (event)=>{
527
- invokeEventHandler(p.onPointerDown, event);
528
- if (!event.defaultPrevented) event.stopPropagation();
529
- }
530
- }), true);
531
- insert(_el$19, ()=>p.children);
532
- return _el$19;
505
+ var _el$19 = _tmpl$();
506
+ var _ref$ = ctx?.setPopoverRef;
507
+ ("function" == typeof _ref$ || Array.isArray(_ref$)) && ref(()=>_ref$, _el$19);
508
+ spread(_el$19, mergeProps(others, ()=>({
509
+ class: twMerge(CLASSES.slot.popover, p.class)
510
+ }), {
511
+ get ["data-theme"] () {
512
+ return p.dataTheme;
513
+ },
514
+ "data-slot": "ui-select-popover",
515
+ get ["data-open"] () {
516
+ return ctx?.open() ? "true" : "false";
517
+ },
518
+ get ["data-placement"] () {
519
+ return overlayPosition.placement();
520
+ },
521
+ get style () {
522
+ return popoverStyle();
523
+ },
524
+ onPointerDown: (event)=>{
525
+ invokeEventHandler(p.onPointerDown, event);
526
+ if (!event.defaultPrevented) event.stopPropagation();
533
527
  }
534
- });
528
+ }), true);
529
+ insert(_el$19, ()=>p.children);
530
+ return _el$19;
535
531
  };
536
532
  const SelectPopover = defineComponent({
537
533
  recipe: componentRecipe,
@@ -1,5 +1,5 @@
1
1
  export declare const CLASSES: {
2
- readonly base: "ui-select";
2
+ readonly base: "ui-select ui-overlay-host";
3
3
  readonly flag: {
4
4
  readonly fullWidth: "ui-select--full-width";
5
5
  };
@@ -1,6 +1,6 @@
1
1
  import { recipe } from "../../lib/layouts/index.js";
2
2
  const CLASSES = {
3
- base: "ui-select",
3
+ base: "ui-select ui-overlay-host",
4
4
  flag: {
5
5
  fullWidth: "ui-select--full-width"
6
6
  },
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "format": "solid-layouts-library-v2",
3
3
  "package": "@pathscale/ui",
4
- "version": "2.11.5",
4
+ "version": "2.11.7",
5
5
  "components": {
6
6
  "Accordion": {
7
7
  "kind": "embedded"
@@ -11255,7 +11255,8 @@
11255
11255
  "component": "Select",
11256
11256
  "classes": {
11257
11257
  "always": [
11258
- "ui-select"
11258
+ "ui-select",
11259
+ "ui-overlay-host"
11259
11260
  ],
11260
11261
  "byProp": {
11261
11262
  "fullWidth": [
@@ -15150,7 +15151,8 @@
15150
15151
  "component": "Dropdown",
15151
15152
  "classes": {
15152
15153
  "always": [
15153
- "dropdown"
15154
+ "dropdown",
15155
+ "ui-overlay-host"
15154
15156
  ],
15155
15157
  "byProp": {
15156
15158
  "slot": {
package/package.json CHANGED
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "@pathscale/ui",
3
- "version": "2.11.5",
3
+ "version": "2.11.7",
4
4
  "author": "pathscale",
5
5
  "repository": {
6
6
  "type": "git",
7
7
  "url": "https://github.com/pathscale/ui"
8
8
  },
9
9
  "license": "MIT",
10
- "description": "Highly opinionated SolidJS component library batteries and kitchen sink included, but optimized and shiny.",
10
+ "description": "Highly opinionated SolidJS component library \u2014 batteries and kitchen sink included, but optimized and shiny.",
11
11
  "files": [
12
12
  "dist"
13
13
  ],
@@ -73,27 +73,30 @@
73
73
  "@rsbuild/core": "^2.0.9",
74
74
  "@rsbuild/plugin-solid": "^1.2.1",
75
75
  "@rslib/core": "^0.22.0",
76
- "@solidjs/h": "2.0.0-rc.0",
76
+ "@solidjs/h": "^2.0.0-rc.0",
77
77
  "@standard-schema/spec": "^1.1.0",
78
+ "@tailwindcss/postcss": "^4.3.3",
78
79
  "@types/bun": "^1.3.14",
79
- "babel-preset-solid": "2.0.0-rc.0",
80
+ "babel-preset-solid": "^2.0.0-rc.0",
80
81
  "cally": "^0.8.0",
81
82
  "cssnano": "^7.1.9",
83
+ "popmotion": "^11.0.5",
82
84
  "postcss": "^8.5.15",
83
85
  "postcss-cli": "^11.0.1",
84
86
  "postcss-selector-parser": "^7.1.1",
85
- "rsbuild-plugin-solid-layouts": "0.2.1",
86
- "solid-js": "2.0.0-rc.0",
87
- "solid-layouts": "0.2.0",
88
- "solid-layouts-oxc": "0.2.1",
87
+ "rsbuild-plugin-solid-layouts": "^0.2.1",
88
+ "solid-js": "^2.0.0-rc.0",
89
+ "solid-layouts": "^0.2.1",
90
+ "solid-layouts-oxc": "^0.2.1",
89
91
  "svgo": "^3.3.3",
92
+ "tailwindcss": "^4.3.3",
90
93
  "typescript": "^6.0.3"
91
94
  },
92
95
  "overrides": {
93
- "babel-preset-solid": "2.0.0-rc.0"
96
+ "babel-preset-solid": "^2.0.0-rc.0"
94
97
  },
95
98
  "dependencies": {
96
- "@solidjs/web": "2.0.0-rc.0",
99
+ "@solidjs/web": "^2.0.0-rc.0",
97
100
  "clsx": "^2.1.1",
98
101
  "tailwind-merge": "^3.6.0"
99
102
  },
@@ -121,14 +124,19 @@
121
124
  "clean": "rm -rf dist node_modules",
122
125
  "format": "bun biome format --write",
123
126
  "lint": "bun run lint:code && bun run lint:layouts",
124
- "lint:code": "bun biome lint --write",
127
+ "lint:code": "bun biome lint --write src qa",
125
128
  "lint:layouts": "solid-layouts-lint",
126
129
  "check": "bun run layouts:generate && bun run scripts/check-contracts.ts",
127
130
  "check:package": "bun run scripts/check-package.ts",
128
131
  "next-version": "bun run scripts/next-version.ts",
129
132
  "smoke": "bun run scripts/smoke-consumer.ts",
133
+ "qa:checks": "bun run tests/qa-harness/generate-checks.ts",
134
+ "qa:entries": "bun run tests/qa-harness/generate-entries.ts",
135
+ "qa:dev": "rsbuild dev --config tests/qa-harness/rsbuild.config.ts",
136
+ "qa:build": "bun run qa:entries && rsbuild build --config tests/qa-harness/rsbuild.config.ts",
130
137
  "test": "bun test --conditions=browser",
131
- "check:api": "bun run scripts/check-api-contract.ts"
138
+ "check:api": "bun run scripts/check-api-contract.ts",
139
+ "qa:lint": "bun biome lint tests/qa-harness"
132
140
  },
133
141
  "sideEffects": [
134
142
  "**/*.css"