phlex_kit 0.7.0 → 0.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.
- checksums.yaml +4 -4
- data/app/assets/stylesheets/phlex_kit/_tokens.css +10 -2
- data/app/assets/stylesheets/phlex_kit/themes/claude.css +1 -1
- data/app/assets/stylesheets/phlex_kit/themes/neutral.css +1 -1
- data/app/assets/stylesheets/phlex_kit/themes/zinc.css +1 -1
- data/app/components/phlex_kit/dropdown_menu/dropdown_menu_controller.js +3 -1
- data/app/components/phlex_kit/menubar/menubar_controller.js +5 -4
- data/app/components/phlex_kit/popover/popover.css +3 -3
- data/app/components/phlex_kit/popover/popover_controller.js +29 -6
- data/app/components/phlex_kit/popover/popover_trigger.rb +4 -1
- data/app/components/phlex_kit/select/select_controller.js +19 -10
- data/lib/phlex_kit/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f17bf0029690be129f5e5d77449b9ebb5632d22151ed3508789683b135a7e03f
|
|
4
|
+
data.tar.gz: 2145e97971f4fba1e283d979f454562839a76bae767c29755d3e9ff01639e306
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 67b7a4e80b51796f3bfc1177be569bf13d064e01b491c5be427baf7275ca87f4fd45d243dfd5f3bdf49fd48735b00fe084ea17e93d48cca62d5b28401c33ca0e
|
|
7
|
+
data.tar.gz: fd0c992227463320e3869ac7917b8ac63c7bbbd5bc468388de7ef00cbec003e0f333c37dde32711c8fed263bb95abbdde2b9bef1789990e8070f4b2b6fc16d29
|
|
@@ -110,8 +110,16 @@
|
|
|
110
110
|
|
|
111
111
|
Dark is the default; light is opt-in via <html data-theme="light"> (set it
|
|
112
112
|
server-side to avoid a flash). "system" follows the OS via prefers-color-scheme.
|
|
113
|
-
Keep the two light token sets in sync.
|
|
114
|
-
|
|
113
|
+
Keep the two light token sets in sync.
|
|
114
|
+
|
|
115
|
+
.pk-dark forces a SUBTREE dark on any page theme (shadcn's .dark wrapper):
|
|
116
|
+
element-level custom properties beat the :root light overrides for every
|
|
117
|
+
descendant. Panels opened from inside the subtree (popovers, dialogs)
|
|
118
|
+
inherit it — they stay DOM children even in the top layer. Caveat:
|
|
119
|
+
component rules forked on :root[data-theme="light"] still match inside
|
|
120
|
+
the island, but they are written in tokens, so they resolve to the
|
|
121
|
+
island's dark values. */
|
|
122
|
+
:root, .pk-dark {
|
|
115
123
|
--pk-bg: #0a0a0a;
|
|
116
124
|
--pk-surface: #171717;
|
|
117
125
|
--pk-surface-2: #262626;
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
Anthropic's brand faces (Styrene, Tiempos/Copernicus) are licensed and
|
|
11
11
|
not shipped; --pk-font-serif falls back through Georgia. A host that has
|
|
12
12
|
the real fonts re-points the token. */
|
|
13
|
-
:root {
|
|
13
|
+
:root, .pk-dark {
|
|
14
14
|
--pk-bg: #262624;
|
|
15
15
|
--pk-surface: #30302e;
|
|
16
16
|
--pk-surface-2: #3a3a37;
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
- dark text-2 has no shadcn source (every dark *-foreground is 0.985);
|
|
20
20
|
chart-1's 0.87 step (neutral-300) is used, matching the kit's own dark
|
|
21
21
|
text-2 weight. */
|
|
22
|
-
:root {
|
|
22
|
+
:root, .pk-dark {
|
|
23
23
|
--pk-bg: oklch(0.145 0 0);
|
|
24
24
|
--pk-surface: oklch(0.205 0 0);
|
|
25
25
|
--pk-surface-2: oklch(0.269 0 0);
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
through to _tokens.css defaults.
|
|
16
16
|
- dark text-2 has no shadcn source (every dark *-foreground is 0.985);
|
|
17
17
|
zinc-300 is used, matching the kit's own dark text-2 weight. */
|
|
18
|
-
:root {
|
|
18
|
+
:root, .pk-dark {
|
|
19
19
|
--pk-bg: oklch(0.141 0.005 285.823);
|
|
20
20
|
--pk-surface: oklch(0.21 0.006 285.885);
|
|
21
21
|
--pk-surface-2: oklch(0.274 0.006 286.033);
|
|
@@ -19,7 +19,9 @@ export default class extends Controller {
|
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
onClickOutside(event) {
|
|
22
|
-
|
|
22
|
+
// Gate on the live :popover-open state, not the stored flag — a stale
|
|
23
|
+
// flag is how a close on an already-closed panel becomes an open.
|
|
24
|
+
if (!this.contentTarget.matches(":popover-open")) return;
|
|
23
25
|
if (this.element.contains(event.target)) return;
|
|
24
26
|
|
|
25
27
|
event.preventDefault();
|
|
@@ -14,8 +14,11 @@ import { Controller } from "@hotwired/stimulus"
|
|
|
14
14
|
export default class extends Controller {
|
|
15
15
|
static targets = ["menu"]
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
// Which menu is open derives from the live :popover-open state, never a
|
|
18
|
+
// stored field — a stale field would make toggle() call showPopover() on
|
|
19
|
+
// an already-open panel (InvalidStateError) or strand an open one.
|
|
20
|
+
get openMenu() {
|
|
21
|
+
return this.menuTargets.find((menu) => this.panel(menu)?.matches(":popover-open")) ?? null
|
|
19
22
|
}
|
|
20
23
|
|
|
21
24
|
disconnect() {
|
|
@@ -57,7 +60,6 @@ export default class extends Controller {
|
|
|
57
60
|
this.close()
|
|
58
61
|
this.panel(menu)?.showPopover()
|
|
59
62
|
menu.querySelector("[aria-expanded]")?.setAttribute("aria-expanded", "true")
|
|
60
|
-
this.openMenu = menu
|
|
61
63
|
}
|
|
62
64
|
if (focus) this.items(menu)[0]?.focus()
|
|
63
65
|
}
|
|
@@ -70,7 +72,6 @@ export default class extends Controller {
|
|
|
70
72
|
if (panel?.matches(":popover-open")) panel.hidePopover()
|
|
71
73
|
const trigger = menu.querySelector("[aria-expanded]")
|
|
72
74
|
trigger?.setAttribute("aria-expanded", "false")
|
|
73
|
-
this.openMenu = null
|
|
74
75
|
if (opts.refocus === true) trigger?.focus()
|
|
75
76
|
}
|
|
76
77
|
|
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
(title + muted description) stacks at gap-0.5. `align: :end` flips the
|
|
4
4
|
panel to the trigger's end edge. Tokens global.
|
|
5
5
|
The panel is a native [popover] in the top layer, anchor-positioned to
|
|
6
|
-
the trigger (Baseline 2026); position-try-fallbacks flips it
|
|
7
|
-
|
|
6
|
+
the trigger (Baseline 2026); position-try-fallbacks flips it on either
|
|
7
|
+
axis (or both) when it would overflow a viewport edge. anchor-scope keeps
|
|
8
8
|
sibling popovers from resolving to each other's anchors. */
|
|
9
9
|
.pk-popover { display: inline-block; anchor-scope: --pk-popover; }
|
|
10
10
|
.pk-popover-trigger { display: inline-block; anchor-name: --pk-popover; }
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
inset: auto;
|
|
15
15
|
position-area: block-end span-inline-end;
|
|
16
16
|
margin: .25rem 0 0;
|
|
17
|
-
position-try-fallbacks: flip-block;
|
|
17
|
+
position-try-fallbacks: flip-block, flip-inline, flip-block flip-inline;
|
|
18
18
|
flex-direction: column; gap: .625rem;
|
|
19
19
|
width: 18rem;
|
|
20
20
|
border: 1px solid color-mix(in oklab, var(--pk-text) 10%, transparent);
|
|
@@ -2,14 +2,37 @@ import { Controller } from "@hotwired/stimulus"
|
|
|
2
2
|
|
|
3
3
|
// Connects to data-controller="phlex-kit--popover". The panel is a native
|
|
4
4
|
// [popover=auto] — the browser owns light dismiss + Escape; CSS anchor
|
|
5
|
-
// positioning places it (popover.css).
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
5
|
+
// positioning places it (popover.css). Toggling is native too: connect()
|
|
6
|
+
// points the trigger's button at the panel via popoverTargetElement, so the
|
|
7
|
+
// browser handles mouse + keyboard activation and exposes the expanded
|
|
8
|
+
// state to assistive tech — no imperative toggle, no invoker/light-dismiss
|
|
9
|
+
// race. Button-less triggers (the date picker's input) fall back to a
|
|
10
|
+
// click toggle; wasOpen records the open state at pointerdown because
|
|
11
|
+
// light dismiss fires first and the click would otherwise reopen.
|
|
9
12
|
export default class extends Controller {
|
|
10
13
|
static targets = ["trigger", "content"]
|
|
11
|
-
|
|
12
|
-
|
|
14
|
+
|
|
15
|
+
connect() {
|
|
16
|
+
const invoker = this.triggerTarget.querySelector("button")
|
|
17
|
+
if (invoker) {
|
|
18
|
+
invoker.popoverTargetElement = this.contentTarget
|
|
19
|
+
} else {
|
|
20
|
+
this.triggerTarget.addEventListener("pointerdown", this.armToggle)
|
|
21
|
+
this.triggerTarget.addEventListener("click", this.toggle)
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
disconnect() {
|
|
26
|
+
this.triggerTarget.removeEventListener("pointerdown", this.armToggle)
|
|
27
|
+
this.triggerTarget.removeEventListener("click", this.toggle)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
armToggle = () => { this.wasOpen = this.contentTarget.matches(":popover-open") }
|
|
31
|
+
toggle = () => {
|
|
32
|
+
if (this.wasOpen) { this.wasOpen = false; return }
|
|
33
|
+
this.contentTarget.togglePopover()
|
|
34
|
+
}
|
|
35
|
+
|
|
13
36
|
contentTargetConnected(el) {
|
|
14
37
|
el.addEventListener("toggle", (e) => { el.dataset.state = e.newState === "open" ? "open" : "closed" })
|
|
15
38
|
}
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
module PhlexKit
|
|
2
|
+
# Wrapper the panel anchors to. Carries no actions: the controller wires
|
|
3
|
+
# the button inside it to the panel via popoverTargetElement, so the
|
|
4
|
+
# browser owns toggling (mouse + keyboard). See popover.rb.
|
|
2
5
|
class PopoverTrigger < BaseComponent
|
|
3
6
|
def initialize(**attrs) = (@attrs = attrs)
|
|
4
7
|
def view_template(&)
|
|
5
|
-
div(**mix({ class: "pk-popover-trigger", data: { phlex_kit__popover_target: "trigger"
|
|
8
|
+
div(**mix({ class: "pk-popover-trigger", data: { phlex_kit__popover_target: "trigger" } }, @attrs), &)
|
|
6
9
|
end
|
|
7
10
|
end
|
|
8
11
|
end
|
|
@@ -89,21 +89,30 @@ export default class extends Controller {
|
|
|
89
89
|
}
|
|
90
90
|
|
|
91
91
|
clickOutside(event) {
|
|
92
|
-
if (!this.
|
|
92
|
+
if (!this.contentTarget.matches(":popover-open")) return;
|
|
93
93
|
if (this.element.contains(event.target)) return;
|
|
94
94
|
|
|
95
95
|
event.preventDefault();
|
|
96
|
-
this
|
|
96
|
+
this.#hide();
|
|
97
97
|
}
|
|
98
98
|
|
|
99
|
+
// Open/close derive from the live :popover-open state, never a stored
|
|
100
|
+
// flag — a stale flag is how a close on an already-closed panel becomes
|
|
101
|
+
// an open (bit the popover's keyboard toggle).
|
|
99
102
|
toogleContent() {
|
|
100
|
-
this.
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
this.triggerTarget.setAttribute("aria-expanded",
|
|
103
|
+
this.contentTarget.matches(":popover-open") ? this.#hide() : this.#show();
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
#show() {
|
|
107
|
+
this.openValue = true;
|
|
108
|
+
this.contentTarget.showPopover();
|
|
109
|
+
this.triggerTarget.setAttribute("aria-expanded", "true");
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
#hide() {
|
|
113
|
+
this.openValue = false;
|
|
114
|
+
if (this.contentTarget.matches(":popover-open")) this.contentTarget.hidePopover();
|
|
115
|
+
this.triggerTarget.setAttribute("aria-expanded", "false");
|
|
107
116
|
}
|
|
108
117
|
|
|
109
118
|
generateItemsIds() {
|
|
@@ -123,7 +132,7 @@ export default class extends Controller {
|
|
|
123
132
|
}
|
|
124
133
|
|
|
125
134
|
closeContent() {
|
|
126
|
-
this
|
|
135
|
+
this.#hide();
|
|
127
136
|
this.resetCurrent();
|
|
128
137
|
|
|
129
138
|
// aria-activedescendant holds an element id; on close it must be removed,
|
data/lib/phlex_kit/version.rb
CHANGED