phlex_kit 0.9.1 → 0.11.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 +8 -0
- data/app/assets/stylesheets/phlex_kit/phlex_kit.css +1 -0
- data/app/components/phlex_kit/accordion/accordion.css +7 -0
- data/app/components/phlex_kit/alert/alert.rb +1 -1
- data/app/components/phlex_kit/alert_dialog/alert_dialog_controller.js +15 -0
- data/app/components/phlex_kit/attachment/attachment.css +6 -3
- data/app/components/phlex_kit/attachment/attachment_group.rb +6 -1
- data/app/components/phlex_kit/attachment/attachment_trigger.rb +5 -0
- data/app/components/phlex_kit/attachment/scroll_fade_controller.js +7 -2
- data/app/components/phlex_kit/avatar/avatar.css +2 -2
- data/app/components/phlex_kit/avatar/avatar_badge.rb +9 -2
- data/app/components/phlex_kit/badge/badge.css +1 -1
- data/app/components/phlex_kit/breadcrumb/breadcrumb.css +0 -4
- data/app/components/phlex_kit/bubble/bubble.css +1 -1
- data/app/components/phlex_kit/bubble/bubble.rb +4 -6
- data/app/components/phlex_kit/bubble/bubble_content.rb +4 -3
- data/app/components/phlex_kit/bubble/bubble_reactions.rb +4 -6
- data/app/components/phlex_kit/button_group/button_group.css +16 -16
- data/app/components/phlex_kit/calendar/calendar.css +10 -2
- data/app/components/phlex_kit/calendar/calendar_controller.js +33 -9
- data/app/components/phlex_kit/carousel/carousel.css +4 -4
- data/app/components/phlex_kit/carousel/carousel.rb +3 -2
- data/app/components/phlex_kit/carousel/carousel_controller.js +23 -2
- data/app/components/phlex_kit/chart/chart.rb +8 -2
- data/app/components/phlex_kit/clipboard/clipboard_controller.js +26 -3
- data/app/components/phlex_kit/clipboard/clipboard_trigger.rb +1 -1
- data/app/components/phlex_kit/codeblock/codeblock.rb +7 -1
- data/app/components/phlex_kit/collapsible/collapsible.rb +4 -1
- data/app/components/phlex_kit/collapsible/collapsible_controller.js +15 -6
- data/app/components/phlex_kit/collapsible/collapsible_trigger.rb +5 -2
- data/app/components/phlex_kit/combobox/combobox.css +3 -3
- data/app/components/phlex_kit/combobox/combobox.rb +3 -3
- data/app/components/phlex_kit/combobox/combobox_badge_trigger.rb +6 -2
- data/app/components/phlex_kit/combobox/combobox_controller.js +32 -10
- data/app/components/phlex_kit/combobox/combobox_input_trigger.rb +4 -1
- data/app/components/phlex_kit/combobox/combobox_list.rb +6 -2
- data/app/components/phlex_kit/combobox/combobox_search_input.rb +3 -1
- data/app/components/phlex_kit/combobox/combobox_toggle_all_checkbox.rb +7 -1
- data/app/components/phlex_kit/command/command.css +2 -2
- data/app/components/phlex_kit/command/command_controller.js +63 -35
- data/app/components/phlex_kit/command/command_dialog_controller.js +30 -1
- data/app/components/phlex_kit/command/command_group.rb +5 -2
- data/app/components/phlex_kit/command/command_item.rb +4 -0
- data/app/components/phlex_kit/command/command_list.rb +6 -2
- data/app/components/phlex_kit/context_menu/context_menu_controller.js +25 -0
- data/app/components/phlex_kit/context_menu/context_menu_sub.rb +3 -1
- data/app/components/phlex_kit/context_menu/context_menu_sub_trigger.rb +3 -1
- data/app/components/phlex_kit/data_table/data_table.css +0 -10
- data/app/components/phlex_kit/data_table/data_table_column_toggle.rb +4 -1
- data/app/components/phlex_kit/data_table/data_table_controller.js +4 -0
- data/app/components/phlex_kit/date_picker/date_picker.rb +6 -0
- data/app/components/phlex_kit/dialog/dialog.css +5 -3
- data/app/components/phlex_kit/dialog/dialog_close.rb +1 -1
- data/app/components/phlex_kit/dialog/dialog_content.rb +1 -1
- data/app/components/phlex_kit/dialog/dialog_controller.js +22 -2
- data/app/components/phlex_kit/drawer/drawer.css +4 -4
- data/app/components/phlex_kit/dropdown_menu/dropdown_menu.css +5 -2
- data/app/components/phlex_kit/dropdown_menu/dropdown_menu_controller.js +27 -0
- data/app/components/phlex_kit/dropdown_menu/dropdown_menu_sub.rb +3 -1
- data/app/components/phlex_kit/dropdown_menu/dropdown_menu_sub_trigger.rb +3 -1
- data/app/components/phlex_kit/empty/empty.css +1 -1
- data/app/components/phlex_kit/field/field.css +2 -1
- data/app/components/phlex_kit/field/field_separator.rb +1 -1
- data/app/components/phlex_kit/form_field/form_field_controller.js +29 -1
- data/app/components/phlex_kit/form_field/form_field_hint.rb +6 -2
- data/app/components/phlex_kit/hover_card/hover_card.css +4 -2
- data/app/components/phlex_kit/icon/icon.css +9 -0
- data/app/components/phlex_kit/icon/icon.rb +7 -1
- data/app/components/phlex_kit/input_group/input_group.css +0 -1
- data/app/components/phlex_kit/input_otp/input_otp.css +4 -4
- data/app/components/phlex_kit/marker/marker.rb +15 -2
- data/app/components/phlex_kit/masked_input/masked_input_controller.js +35 -15
- data/app/components/phlex_kit/menubar/menubar.rb +3 -1
- data/app/components/phlex_kit/menubar/menubar_controller.js +28 -1
- data/app/components/phlex_kit/menubar/menubar_item.rb +4 -2
- data/app/components/phlex_kit/menubar/menubar_radio_group.rb +4 -2
- data/app/components/phlex_kit/message/message.rb +5 -0
- data/app/components/phlex_kit/message_scroller/message_scroller_controller.js +12 -5
- data/app/components/phlex_kit/native_select/native_select.css +4 -2
- data/app/components/phlex_kit/native_select/native_select.rb +12 -3
- data/app/components/phlex_kit/navigation_menu/navigation_menu.rb +7 -1
- data/app/components/phlex_kit/pagination/pagination.rb +10 -2
- data/app/components/phlex_kit/pagination/pagination_next.rb +11 -2
- data/app/components/phlex_kit/pagination/pagination_previous.rb +11 -2
- data/app/components/phlex_kit/popover/popover_controller.js +31 -1
- data/app/components/phlex_kit/progress/progress.css +8 -0
- data/app/components/phlex_kit/progress/progress.rb +14 -5
- data/app/components/phlex_kit/resizable/resizable_controller.js +52 -5
- data/app/components/phlex_kit/resizable/resizable_panel.rb +13 -4
- data/app/components/phlex_kit/scroll_area/scroll_area.rb +23 -4
- data/app/components/phlex_kit/select/select.css +5 -3
- data/app/components/phlex_kit/select/select_content.rb +9 -3
- data/app/components/phlex_kit/select/select_controller.js +59 -6
- data/app/components/phlex_kit/select/select_item.rb +5 -1
- data/app/components/phlex_kit/separator/separator.rb +12 -2
- data/app/components/phlex_kit/sheet/sheet.css +4 -2
- data/app/components/phlex_kit/sheet/sheet_close.rb +1 -1
- data/app/components/phlex_kit/sheet/sheet_content_controller.js +4 -0
- data/app/components/phlex_kit/sheet/sheet_controller.js +15 -0
- data/app/components/phlex_kit/sidebar/sidebar.css +1 -1
- data/app/components/phlex_kit/sidebar/sidebar_controller.js +10 -0
- data/app/components/phlex_kit/sidebar/sidebar_menu_button.rb +9 -1
- data/app/components/phlex_kit/sidebar/sidebar_menu_sub_button.rb +8 -1
- data/app/components/phlex_kit/slider/slider.css +15 -0
- data/app/components/phlex_kit/switch/switch.css +5 -0
- data/app/components/phlex_kit/table/table.css +2 -1
- data/app/components/phlex_kit/table/table.rb +5 -2
- data/app/components/phlex_kit/tabs/tabs.css +2 -2
- data/app/components/phlex_kit/tabs/tabs_content.rb +4 -2
- data/app/components/phlex_kit/tabs/tabs_controller.js +4 -1
- data/app/components/phlex_kit/tabs/tabs_list.rb +7 -2
- data/app/components/phlex_kit/tabs/tabs_trigger.rb +15 -3
- data/app/components/phlex_kit/theme_toggle/theme_toggle_controller.js +14 -0
- data/app/components/phlex_kit/toast/toast.css +3 -2
- data/app/components/phlex_kit/toast/toast_action.rb +4 -1
- data/app/components/phlex_kit/toast/toast_controller.js +13 -5
- data/app/components/phlex_kit/toast/toast_region.rb +19 -9
- data/app/components/phlex_kit/toast/toaster_controller.js +34 -12
- data/app/components/phlex_kit/toggle_group/toggle_group.css +7 -7
- data/app/components/phlex_kit/toggle_group/toggle_group.rb +7 -2
- data/app/components/phlex_kit/toggle_group/toggle_group_controller.js +13 -1
- data/app/components/phlex_kit/toggle_group/toggle_group_item.rb +9 -1
- data/app/components/phlex_kit/tooltip/tooltip.css +7 -3
- data/app/components/phlex_kit/tooltip/tooltip_controller.js +7 -1
- data/app/components/phlex_kit/typography/heading.rb +13 -5
- data/app/components/phlex_kit/typography/list.rb +22 -0
- data/app/components/phlex_kit/typography/text.rb +7 -1
- data/lib/phlex_kit/base_component.rb +13 -0
- data/lib/phlex_kit/version.rb +1 -1
- metadata +3 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: caadd4e868a63939f22013699b89f9b1557dfa9569022301602c0f1cb07020f0
|
|
4
|
+
data.tar.gz: fa3ba42e9566f171e1fdee7ddcbfc475397bf404152031adbca2fd0cacbbda61
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 76304ba104026fe236758b3fa93014b516ec84e65e6baefd307853da30bb7dc9d38d1f6fff0d2c7639ede3ee9116642beed8a615c24963ea3e0462c0ca89656a
|
|
7
|
+
data.tar.gz: 0c8d0dfd56d4e4b873b35149dd7c362010c97a02a4642de0b72d80606301be27dea505849753aa8754e081c38e49484b3358524267d3df497a12b8c5d9e9ccd0
|
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
/* Kit utilities used by the interactive controllers (Stimulus toggles these). */
|
|
2
2
|
.pk-hidden { display: none !important; }
|
|
3
3
|
|
|
4
|
+
/* Kit-wide screen-reader-only utility — used by many components
|
|
5
|
+
(breadcrumb, dialog, sheet, sidebar, …), so it lives here rather
|
|
6
|
+
than in any one component's stylesheet. */
|
|
7
|
+
.pk-sr-only {
|
|
8
|
+
position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
|
|
9
|
+
overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
|
|
10
|
+
}
|
|
11
|
+
|
|
4
12
|
/* Shimmer — shadcn's shimmer util: a highlight band sweeping across text, for
|
|
5
13
|
loading / "thinking…" states. Pure CSS, built on currentColor: the text keeps
|
|
6
14
|
its own color (any color) and the highlight derives from it via relative
|
|
@@ -44,6 +44,7 @@
|
|
|
44
44
|
@import url("form/form.css");
|
|
45
45
|
@import url("form_field/form_field.css");
|
|
46
46
|
@import url("hover_card/hover_card.css");
|
|
47
|
+
@import url("icon/icon.css");
|
|
47
48
|
@import url("input/input.css");
|
|
48
49
|
@import url("input_group/input_group.css");
|
|
49
50
|
@import url("input_otp/input_otp.css");
|
|
@@ -9,6 +9,13 @@
|
|
|
9
9
|
background: none; border: 0; cursor: pointer; color: inherit; transition: color .15s ease, background-color .15s ease, text-decoration-color .15s ease;
|
|
10
10
|
}
|
|
11
11
|
.pk-accordion-trigger:hover { text-decoration: underline; }
|
|
12
|
+
/* background/border are stripped above — reinstate the kit focus ring or
|
|
13
|
+
keyboard focus falls back to the UA outline. */
|
|
14
|
+
.pk-accordion-trigger:focus-visible {
|
|
15
|
+
outline: none;
|
|
16
|
+
border-radius: calc(var(--pk-radius) - 2px);
|
|
17
|
+
box-shadow: 0 0 0 3px color-mix(in oklab, var(--pk-ring) 50%, transparent);
|
|
18
|
+
}
|
|
12
19
|
.pk-accordion-trigger-label { flex: 1; }
|
|
13
20
|
.pk-accordion-icon { display: inline-flex; opacity: .5; transition: transform .2s ease; }
|
|
14
21
|
.pk-accordion-icon svg { width: 1rem; height: 1rem; }
|
|
@@ -15,7 +15,7 @@ module PhlexKit
|
|
|
15
15
|
# shadcn ships default/destructive; success/warning are kit extras kept from
|
|
16
16
|
# ruby_ui, restyled to the same text-tint grammar. `VARIANTS.fetch` fails loud.
|
|
17
17
|
class Alert < BaseComponent
|
|
18
|
-
# variant => extra class appended after the base `
|
|
18
|
+
# variant => extra class appended after the base `pk-alert` (nil = neutral).
|
|
19
19
|
VARIANTS = {
|
|
20
20
|
default: nil,
|
|
21
21
|
warning: "warning",
|
|
@@ -52,6 +52,9 @@ export default class extends Controller {
|
|
|
52
52
|
// Double-open guard: Enter+click (or a double-click) on the trigger must
|
|
53
53
|
// not stack a second clone over the first.
|
|
54
54
|
if (this.clone?.isConnected) return;
|
|
55
|
+
// open: is one-shot — the reflected value sits in the Turbo snapshot, so
|
|
56
|
+
// a cache-restored reconnect would re-open a dialog the user dismissed.
|
|
57
|
+
this.openValue = false;
|
|
55
58
|
document.body.insertAdjacentHTML("beforeend", this.contentTarget.innerHTML);
|
|
56
59
|
this.clone = document.body.lastElementChild;
|
|
57
60
|
}
|
|
@@ -68,6 +71,10 @@ export default class extends Controller {
|
|
|
68
71
|
}
|
|
69
72
|
|
|
70
73
|
keydown(event) {
|
|
74
|
+
// Every open clone has its own document-level listener; with stacked
|
|
75
|
+
// alert dialogs (a trigger inside a panel) only the topmost clone may
|
|
76
|
+
// handle the keyboard, or one Escape would close every layer at once.
|
|
77
|
+
if (!this.#topmost()) return;
|
|
71
78
|
if (event.key === "Escape") {
|
|
72
79
|
event.preventDefault();
|
|
73
80
|
this.dismiss();
|
|
@@ -97,6 +104,14 @@ export default class extends Controller {
|
|
|
97
104
|
}
|
|
98
105
|
}
|
|
99
106
|
|
|
107
|
+
// Clones are appended as direct <body> children whose direct child is the
|
|
108
|
+
// [role="alertdialog"] panel (source elements only hold a <template>, whose
|
|
109
|
+
// content never matches querySelector). The last such clone is the topmost.
|
|
110
|
+
#topmost() {
|
|
111
|
+
const clones = document.body.querySelectorAll(':scope > [data-controller~="phlex-kit--alert-dialog"] > [role="alertdialog"]');
|
|
112
|
+
return clones.length > 0 && clones[clones.length - 1].parentElement === this.element;
|
|
113
|
+
}
|
|
114
|
+
|
|
100
115
|
#panel() {
|
|
101
116
|
return this.element.querySelector('[role="alertdialog"]');
|
|
102
117
|
}
|
|
@@ -142,21 +142,24 @@
|
|
|
142
142
|
/* Scrollable, snapping row with an edge fade (horizontal scroll-fade). */
|
|
143
143
|
.pk-attachment-group {
|
|
144
144
|
--pk-scroll-fade-size: 2rem;
|
|
145
|
+
/* Gradient directions can't be logical — follow the inline axis by hand. */
|
|
146
|
+
--pk-scroll-fade-dir: to right;
|
|
145
147
|
display: flex;
|
|
146
148
|
gap: .5rem;
|
|
147
149
|
overflow-x: auto;
|
|
148
150
|
scroll-snap-type: x mandatory;
|
|
149
151
|
padding: 2px; /* room for card rings */
|
|
150
|
-
mask-image: linear-gradient(
|
|
152
|
+
mask-image: linear-gradient(var(--pk-scroll-fade-dir),
|
|
151
153
|
transparent 0, black var(--pk-scroll-fade-size),
|
|
152
154
|
black calc(100% - var(--pk-scroll-fade-size)), transparent 100%);
|
|
153
155
|
}
|
|
156
|
+
.pk-attachment-group:dir(rtl) { --pk-scroll-fade-dir: to left; }
|
|
154
157
|
.pk-attachment-group[data-at-start] {
|
|
155
|
-
mask-image: linear-gradient(
|
|
158
|
+
mask-image: linear-gradient(var(--pk-scroll-fade-dir),
|
|
156
159
|
black calc(100% - var(--pk-scroll-fade-size)), transparent 100%);
|
|
157
160
|
}
|
|
158
161
|
.pk-attachment-group[data-at-end] {
|
|
159
|
-
mask-image: linear-gradient(
|
|
162
|
+
mask-image: linear-gradient(var(--pk-scroll-fade-dir), transparent 0, black var(--pk-scroll-fade-size));
|
|
160
163
|
}
|
|
161
164
|
.pk-attachment-group[data-at-start][data-at-end] { mask-image: none; }
|
|
162
165
|
.pk-attachment-group > .pk-attachment { flex: none; scroll-snap-align: start; }
|
|
@@ -11,9 +11,14 @@ module PhlexKit
|
|
|
11
11
|
div(**mix({
|
|
12
12
|
class: "pk-attachment-group",
|
|
13
13
|
role: "group",
|
|
14
|
+
# at-start + at-end = no edge fades. Server-rendered so a group that
|
|
15
|
+
# FITS shows no false "more content" fade pre-hydration / with JS off;
|
|
16
|
+
# the controller's first update() removes them when there's overflow.
|
|
14
17
|
data: {
|
|
15
18
|
controller: "phlex-kit--scroll-fade",
|
|
16
|
-
phlex_kit__scroll_fade_axis_value: "x"
|
|
19
|
+
phlex_kit__scroll_fade_axis_value: "x",
|
|
20
|
+
at_start: "",
|
|
21
|
+
at_end: ""
|
|
17
22
|
}
|
|
18
23
|
}, @attrs), &)
|
|
19
24
|
end
|
|
@@ -4,8 +4,13 @@ module PhlexKit
|
|
|
4
4
|
# stay independently clickable. Give it an aria-label; pass `as: :a` +
|
|
5
5
|
# `href:` for a link. See attachment.rb.
|
|
6
6
|
class AttachmentTrigger < BaseComponent
|
|
7
|
+
AS_TAGS = %i[button a].freeze
|
|
8
|
+
|
|
7
9
|
def initialize(as: :button, href: nil, **attrs)
|
|
8
10
|
@as = as.to_sym
|
|
11
|
+
unless AS_TAGS.include?(@as)
|
|
12
|
+
raise ArgumentError, "unknown AttachmentTrigger as: #{@as.inspect} (use one of #{AS_TAGS.join(", ")})"
|
|
13
|
+
end
|
|
9
14
|
@href = href
|
|
10
15
|
@attrs = attrs
|
|
11
16
|
end
|
|
@@ -29,8 +29,13 @@ export default class extends Controller {
|
|
|
29
29
|
update() {
|
|
30
30
|
const el = this.element
|
|
31
31
|
if (this.axisValue === "x") {
|
|
32
|
-
|
|
33
|
-
|
|
32
|
+
// Math.abs: in RTL scrollLeft runs 0..negative, so the raw value would
|
|
33
|
+
// read as permanently "at start" — abs tracks the logical inline start.
|
|
34
|
+
const scrolled = Math.abs(el.scrollLeft)
|
|
35
|
+
// <= 2, not <= 1: scroll-snap + the 2px ring padding leave the group
|
|
36
|
+
// resting at scrollLeft 2, which must still count as "at start".
|
|
37
|
+
el.toggleAttribute("data-at-start", scrolled <= 2)
|
|
38
|
+
el.toggleAttribute("data-at-end", scrolled + el.clientWidth >= el.scrollWidth - 1)
|
|
34
39
|
} else {
|
|
35
40
|
el.toggleAttribute("data-at-start", el.scrollTop <= 1)
|
|
36
41
|
el.toggleAttribute("data-at-end", el.scrollTop + el.clientHeight >= el.scrollHeight - 1)
|
|
@@ -67,8 +67,8 @@
|
|
|
67
67
|
modifier, glyph hidden when there's no room (shadcn's AvatarBadge). */
|
|
68
68
|
.pk-avatar-badge {
|
|
69
69
|
position: absolute;
|
|
70
|
-
|
|
71
|
-
|
|
70
|
+
inset-inline-end: 0;
|
|
71
|
+
inset-block-end: 0;
|
|
72
72
|
z-index: 10;
|
|
73
73
|
display: inline-flex;
|
|
74
74
|
align-items: center;
|
|
@@ -4,12 +4,19 @@ module PhlexKit
|
|
|
4
4
|
# svg glyph (hidden automatically on xs/sm avatars). Recolor via style/class
|
|
5
5
|
# — defaults to the brand fill. See avatar.rb.
|
|
6
6
|
class AvatarBadge < BaseComponent
|
|
7
|
-
|
|
7
|
+
# label: gives the color-only presence dot an accessible alternative
|
|
8
|
+
# (a .pk-sr-only span, like AttachmentAction's). No default — a purely
|
|
9
|
+
# decorative dot stays silent.
|
|
10
|
+
def initialize(label: nil, **attrs)
|
|
11
|
+
@label = label
|
|
8
12
|
@attrs = attrs
|
|
9
13
|
end
|
|
10
14
|
|
|
11
15
|
def view_template(&block)
|
|
12
|
-
span(**mix({ class: "pk-avatar-badge" }, @attrs)
|
|
16
|
+
span(**mix({ class: "pk-avatar-badge" }, @attrs)) do
|
|
17
|
+
span(class: "pk-sr-only") { @label } if @label
|
|
18
|
+
block&.call
|
|
19
|
+
end
|
|
13
20
|
end
|
|
14
21
|
end
|
|
15
22
|
end
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
overflow: hidden;
|
|
16
16
|
white-space: nowrap;
|
|
17
17
|
height: 1.25rem;
|
|
18
|
-
border-radius:
|
|
18
|
+
border-radius: 999px; /* fully-round pill (matches avatar/progress), not a --pk-radius derivative */
|
|
19
19
|
border: 1px solid transparent;
|
|
20
20
|
padding: .125rem .5rem;
|
|
21
21
|
font-size: .75rem;
|
|
@@ -12,7 +12,3 @@
|
|
|
12
12
|
.pk-breadcrumb-separator > svg { width: .875rem; height: .875rem; }
|
|
13
13
|
.pk-breadcrumb-ellipsis { display: flex; height: 1.25rem; width: 1.25rem; align-items: center; justify-content: center; }
|
|
14
14
|
.pk-breadcrumb-ellipsis > svg { width: 1rem; height: 1rem; }
|
|
15
|
-
.pk-sr-only {
|
|
16
|
-
position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
|
|
17
|
-
overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
|
|
18
|
-
}
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
.pk-message[data-align="end"] .pk-bubble { align-self: flex-end; }
|
|
15
15
|
.pk-bubble-content {
|
|
16
16
|
width: fit-content; max-width: 100%; min-width: 0; overflow: hidden;
|
|
17
|
-
overflow-wrap: break-word; border: 1px solid transparent; border-radius:
|
|
17
|
+
overflow-wrap: break-word; border: 1px solid transparent; border-radius: calc(var(--pk-radius) + 2px);
|
|
18
18
|
padding: .5rem .75rem; font-size: .875rem; line-height: 1.625;
|
|
19
19
|
}
|
|
20
20
|
.pk-bubble[data-align="end"] > .pk-bubble-content { align-self: flex-end; }
|
|
@@ -3,13 +3,11 @@ module PhlexKit
|
|
|
3
3
|
# the child BubbleContent via a data-slot selector; align: :end flips it to the
|
|
4
4
|
# trailing edge. Compose Bubble > BubbleContent (+ BubbleReactions).
|
|
5
5
|
class Bubble < BaseComponent
|
|
6
|
-
VARIANTS = %i[default secondary muted tinted outline ghost destructive].freeze
|
|
7
|
-
ALIGNS = %i[start end].freeze
|
|
6
|
+
VARIANTS = %i[default secondary muted tinted outline ghost destructive].to_h { |v| [ v, v ] }.freeze
|
|
7
|
+
ALIGNS = %i[start end].to_h { |v| [ v, v ] }.freeze
|
|
8
8
|
def initialize(variant: :default, align: :start, **attrs)
|
|
9
|
-
@variant = variant.to_sym
|
|
10
|
-
|
|
11
|
-
@align = align.to_sym
|
|
12
|
-
raise KeyError, "unknown Bubble align #{@align}" unless ALIGNS.include?(@align)
|
|
9
|
+
@variant = fetch_option(VARIANTS, variant.to_sym, :variant)
|
|
10
|
+
@align = fetch_option(ALIGNS, align.to_sym, :align)
|
|
13
11
|
@attrs = attrs
|
|
14
12
|
end
|
|
15
13
|
def view_template(&)
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
module PhlexKit
|
|
2
2
|
class BubbleContent < BaseComponent
|
|
3
|
-
# `as:` is dispatched dynamically — whitelist the
|
|
4
|
-
#
|
|
5
|
-
|
|
3
|
+
# `as:` is dispatched dynamically — whitelist the documented containers
|
|
4
|
+
# (incl. the interactive button/a contract bubble.css styles) so it can't
|
|
5
|
+
# reach arbitrary (including private) methods.
|
|
6
|
+
AS_TAGS = %i[div p blockquote button a].freeze
|
|
6
7
|
def initialize(as: :div, **attrs)
|
|
7
8
|
@as = as.to_sym
|
|
8
9
|
raise ArgumentError, "unknown BubbleContent as: #{@as.inspect} (use one of #{AS_TAGS.join(", ")})" unless AS_TAGS.include?(@as)
|
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
module PhlexKit
|
|
2
2
|
class BubbleReactions < BaseComponent
|
|
3
|
-
SIDES = %i[top bottom].freeze
|
|
4
|
-
ALIGNS = %i[start end].freeze
|
|
3
|
+
SIDES = %i[top bottom].to_h { |v| [ v, v ] }.freeze
|
|
4
|
+
ALIGNS = %i[start end].to_h { |v| [ v, v ] }.freeze
|
|
5
5
|
def initialize(side: :bottom, align: :end, **attrs)
|
|
6
|
-
@side = side.to_sym
|
|
7
|
-
|
|
8
|
-
@align = align.to_sym
|
|
9
|
-
raise KeyError, "unknown BubbleReactions align #{@align}" unless ALIGNS.include?(@align)
|
|
6
|
+
@side = fetch_option(SIDES, side.to_sym, :side)
|
|
7
|
+
@align = fetch_option(ALIGNS, align.to_sym, :align)
|
|
10
8
|
@attrs = attrs
|
|
11
9
|
end
|
|
12
10
|
def view_template(&)
|
|
@@ -12,25 +12,25 @@
|
|
|
12
12
|
|
|
13
13
|
/* horizontal (default) */
|
|
14
14
|
.pk-button-group:not(.vertical) > :not(:first-child) {
|
|
15
|
-
border-
|
|
16
|
-
border-
|
|
15
|
+
border-start-start-radius: 0;
|
|
16
|
+
border-end-start-radius: 0;
|
|
17
17
|
border-inline-start: 0;
|
|
18
18
|
}
|
|
19
19
|
.pk-button-group:not(.vertical) > :not(:last-child) {
|
|
20
|
-
border-
|
|
21
|
-
border-
|
|
20
|
+
border-start-end-radius: 0;
|
|
21
|
+
border-end-end-radius: 0;
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
/* vertical */
|
|
25
25
|
.pk-button-group.vertical { flex-direction: column; }
|
|
26
26
|
.pk-button-group.vertical > :not(:first-child) {
|
|
27
|
-
border-
|
|
28
|
-
border-
|
|
27
|
+
border-start-start-radius: 0;
|
|
28
|
+
border-start-end-radius: 0;
|
|
29
29
|
border-top: 0;
|
|
30
30
|
}
|
|
31
31
|
.pk-button-group.vertical > :not(:last-child) {
|
|
32
|
-
border-
|
|
33
|
-
border-
|
|
32
|
+
border-end-start-radius: 0;
|
|
33
|
+
border-end-end-radius: 0;
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
/* Nested groups keep their own corners — undo the squaring the parent applied. */
|
|
@@ -45,25 +45,25 @@
|
|
|
45
45
|
absolutely-positioned panels are untouched. */
|
|
46
46
|
.pk-button-group:not(.vertical) > :not(:first-child) > :is(.pk-dropdown-menu-trigger, .pk-popover-trigger, .pk-hover-card-trigger, .pk-tooltip-trigger) > .pk-button,
|
|
47
47
|
.pk-button-group:not(.vertical) > :not(:first-child) > :is(.pk-select-trigger, .pk-combobox-trigger) {
|
|
48
|
-
border-
|
|
49
|
-
border-
|
|
48
|
+
border-start-start-radius: 0;
|
|
49
|
+
border-end-start-radius: 0;
|
|
50
50
|
border-inline-start: 0;
|
|
51
51
|
}
|
|
52
52
|
.pk-button-group:not(.vertical) > :not(:last-child) > :is(.pk-dropdown-menu-trigger, .pk-popover-trigger, .pk-hover-card-trigger, .pk-tooltip-trigger) > .pk-button,
|
|
53
53
|
.pk-button-group:not(.vertical) > :not(:last-child) > :is(.pk-select-trigger, .pk-combobox-trigger) {
|
|
54
|
-
border-
|
|
55
|
-
border-
|
|
54
|
+
border-start-end-radius: 0;
|
|
55
|
+
border-end-end-radius: 0;
|
|
56
56
|
}
|
|
57
57
|
.pk-button-group.vertical > :not(:first-child) > :is(.pk-dropdown-menu-trigger, .pk-popover-trigger, .pk-hover-card-trigger, .pk-tooltip-trigger) > .pk-button,
|
|
58
58
|
.pk-button-group.vertical > :not(:first-child) > :is(.pk-select-trigger, .pk-combobox-trigger) {
|
|
59
|
-
border-
|
|
60
|
-
border-
|
|
59
|
+
border-start-start-radius: 0;
|
|
60
|
+
border-start-end-radius: 0;
|
|
61
61
|
border-top: 0;
|
|
62
62
|
}
|
|
63
63
|
.pk-button-group.vertical > :not(:last-child) > :is(.pk-dropdown-menu-trigger, .pk-popover-trigger, .pk-hover-card-trigger, .pk-tooltip-trigger) > .pk-button,
|
|
64
64
|
.pk-button-group.vertical > :not(:last-child) > :is(.pk-select-trigger, .pk-combobox-trigger) {
|
|
65
|
-
border-
|
|
66
|
-
border-
|
|
65
|
+
border-end-start-radius: 0;
|
|
66
|
+
border-end-end-radius: 0;
|
|
67
67
|
}
|
|
68
68
|
/* .pk-select defaults to width:100%; as a group segment it hugs its trigger
|
|
69
69
|
and lets siblings (the input) take the leftover flex space — shadcn's
|
|
@@ -155,6 +155,14 @@
|
|
|
155
155
|
background: var(--pk-accent);
|
|
156
156
|
}
|
|
157
157
|
.pk-calendar-cell:has(> .range-cap) { background: var(--pk-accent); }
|
|
158
|
-
.pk-calendar-cell:has(> .range-start.range-cap) {
|
|
159
|
-
|
|
158
|
+
.pk-calendar-cell:has(> .range-start.range-cap) {
|
|
159
|
+
border-radius: 0;
|
|
160
|
+
border-start-start-radius: var(--pk-cell-radius);
|
|
161
|
+
border-end-start-radius: var(--pk-cell-radius);
|
|
162
|
+
}
|
|
163
|
+
.pk-calendar-cell:has(> .range-end.range-cap) {
|
|
164
|
+
border-radius: 0;
|
|
165
|
+
border-start-end-radius: var(--pk-cell-radius);
|
|
166
|
+
border-end-end-radius: var(--pk-cell-radius);
|
|
167
|
+
}
|
|
160
168
|
.pk-calendar-cell:has(> .range-start.range-end) { border-radius: var(--pk-cell-radius); }
|
|
@@ -159,7 +159,9 @@ export default class extends Controller {
|
|
|
159
159
|
|
|
160
160
|
toggleMultipleDay(iso) {
|
|
161
161
|
const day = this.isoDate(this.parseDate(iso));
|
|
162
|
-
|
|
162
|
+
// Normalized copy: server-supplied entries may be datetime strings
|
|
163
|
+
// (Time#to_s) — indexOf against the bare isoDate must still match.
|
|
164
|
+
const current = this.normalizeDates(this.selectedDatesValue);
|
|
163
165
|
const index = current.indexOf(day);
|
|
164
166
|
if (index >= 0) {
|
|
165
167
|
current.splice(index, 1);
|
|
@@ -303,8 +305,18 @@ export default class extends Controller {
|
|
|
303
305
|
}
|
|
304
306
|
e.preventDefault();
|
|
305
307
|
|
|
306
|
-
|
|
307
|
-
if (this.isDateDisabled(iso))
|
|
308
|
+
let iso = this.isoDate(target);
|
|
309
|
+
if (this.isDateDisabled(iso)) {
|
|
310
|
+
// A min/max boundary holds focus in place; an isolated booked date is
|
|
311
|
+
// SKIPPED in the travel direction (APG date-grid) — arrows only, the
|
|
312
|
+
// month/row jumps (PageUp/Home/End) still stay put.
|
|
313
|
+
if (!(e.key in STEPS) || this.outOfRange(target)) return;
|
|
314
|
+
do {
|
|
315
|
+
target.setDate(target.getDate() + STEPS[e.key]);
|
|
316
|
+
if (this.outOfRange(target)) return;
|
|
317
|
+
} while (this.isBooked(target));
|
|
318
|
+
iso = this.isoDate(target);
|
|
319
|
+
}
|
|
308
320
|
|
|
309
321
|
if (target.getMonth() !== this.viewDate().getMonth() || target.getFullYear() !== this.viewDate().getFullYear()) {
|
|
310
322
|
this.viewDateValue = iso; // re-renders the grid on the target month
|
|
@@ -412,7 +424,7 @@ export default class extends Controller {
|
|
|
412
424
|
);
|
|
413
425
|
}
|
|
414
426
|
if (this.modeValue === "multiple") {
|
|
415
|
-
return this.selectedDatesValue.includes(this.isoDate(day));
|
|
427
|
+
return this.normalizeDates(this.selectedDatesValue).includes(this.isoDate(day));
|
|
416
428
|
}
|
|
417
429
|
const selectedDate = this.selectedDate();
|
|
418
430
|
return selectedDate && day.toDateString() === selectedDate.toDateString();
|
|
@@ -428,7 +440,17 @@ export default class extends Controller {
|
|
|
428
440
|
}
|
|
429
441
|
|
|
430
442
|
isBooked(day) {
|
|
431
|
-
return this.disabledDatesValue.includes(this.isoDate(day));
|
|
443
|
+
return this.normalizeDates(this.disabledDatesValue).includes(this.isoDate(day));
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
// disabled_dates / selected_dates entries may be Time#to_s or datetime ISO
|
|
447
|
+
// strings — every other date input tolerates those via parseDate, so the
|
|
448
|
+
// membership arrays must be normalized the same way (exact string equality
|
|
449
|
+
// silently no-oped for anything but bare yyyy-MM-dd).
|
|
450
|
+
normalizeDates(values) {
|
|
451
|
+
return values
|
|
452
|
+
.map((d) => { const parsed = this.parseDate(d); return parsed ? this.isoDate(parsed) : null; })
|
|
453
|
+
.filter(Boolean);
|
|
432
454
|
}
|
|
433
455
|
|
|
434
456
|
monthAndYear() {
|
|
@@ -575,14 +597,16 @@ export default class extends Controller {
|
|
|
575
597
|
isDateDisabled(date) {
|
|
576
598
|
const candidate = this.parseDate(date);
|
|
577
599
|
if (!candidate) return false;
|
|
600
|
+
return this.outOfRange(candidate) || this.isBooked(candidate);
|
|
601
|
+
}
|
|
578
602
|
|
|
603
|
+
// min/max bounds only (not booked entries) — the keyboard skip needs to
|
|
604
|
+
// distinguish a hard boundary from a hole in the middle of the grid.
|
|
605
|
+
outOfRange(candidate) {
|
|
579
606
|
const minDate = this.minDate();
|
|
580
607
|
if (minDate && this.startOfDay(candidate) < this.startOfDay(minDate)) return true;
|
|
581
|
-
|
|
582
608
|
const maxDate = this.maxDate();
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
return this.disabledDatesValue.includes(this.isoDate(candidate));
|
|
609
|
+
return !!(maxDate && this.startOfDay(candidate) > this.startOfDay(maxDate));
|
|
586
610
|
}
|
|
587
611
|
|
|
588
612
|
parseDate(value) {
|
|
@@ -16,14 +16,14 @@
|
|
|
16
16
|
transition: transform .3s ease;
|
|
17
17
|
will-change: transform;
|
|
18
18
|
}
|
|
19
|
-
.pk-carousel.is-horizontal .pk-carousel-track { margin-
|
|
19
|
+
.pk-carousel.is-horizontal .pk-carousel-track { margin-inline-start: -1rem; }
|
|
20
20
|
.pk-carousel.is-vertical .pk-carousel-track { flex-direction: column; margin-top: -1rem; }
|
|
21
21
|
|
|
22
22
|
.pk-carousel-item {
|
|
23
23
|
min-width: 0;
|
|
24
24
|
flex: 0 0 100%;
|
|
25
25
|
}
|
|
26
|
-
.pk-carousel.is-horizontal .pk-carousel-item { padding-
|
|
26
|
+
.pk-carousel.is-horizontal .pk-carousel-item { padding-inline-start: 1rem; }
|
|
27
27
|
.pk-carousel.is-vertical .pk-carousel-item { padding-top: 1rem; }
|
|
28
28
|
|
|
29
29
|
/* Edge buttons (an outline icon .pk-button; same specificity, later in the
|
|
@@ -34,8 +34,8 @@
|
|
|
34
34
|
height: 2rem;
|
|
35
35
|
border-radius: 9999px;
|
|
36
36
|
}
|
|
37
|
-
.pk-carousel.is-horizontal .pk-carousel-previous {
|
|
38
|
-
.pk-carousel.is-horizontal .pk-carousel-next {
|
|
37
|
+
.pk-carousel.is-horizontal .pk-carousel-previous { inset-inline-start: -3rem; top: 50%; transform: translateY(-50%); }
|
|
38
|
+
.pk-carousel.is-horizontal .pk-carousel-next { inset-inline-end: -3rem; top: 50%; transform: translateY(-50%); }
|
|
39
39
|
.pk-carousel.is-vertical .pk-carousel-previous { top: -3rem; left: 50%; transform: translateX(-50%) rotate(90deg); }
|
|
40
40
|
.pk-carousel.is-vertical .pk-carousel-next { bottom: -3rem; left: 50%; transform: translateX(-50%) rotate(90deg); }
|
|
41
41
|
.pk-carousel-previous svg, .pk-carousel-next svg { width: 1rem; height: 1rem; }
|
|
@@ -35,12 +35,13 @@ module PhlexKit
|
|
|
35
35
|
end
|
|
36
36
|
|
|
37
37
|
# Arrow keys follow the scroll axis: left/right for horizontal, up/down
|
|
38
|
-
# for vertical.
|
|
38
|
+
# for vertical. Horizontal arrows route through keyNext/keyPrev, which
|
|
39
|
+
# flip in RTL (the next slide sits to the physical left there).
|
|
39
40
|
def keyboard_action
|
|
40
41
|
if @orientation == :vertical
|
|
41
42
|
"keydown.down->phlex-kit--carousel#scrollNext:prevent keydown.up->phlex-kit--carousel#scrollPrev:prevent"
|
|
42
43
|
else
|
|
43
|
-
"keydown.right->phlex-kit--carousel#
|
|
44
|
+
"keydown.right->phlex-kit--carousel#keyNext:prevent keydown.left->phlex-kit--carousel#keyPrev:prevent"
|
|
44
45
|
end
|
|
45
46
|
end
|
|
46
47
|
end
|
|
@@ -73,6 +73,16 @@ export default class extends Controller {
|
|
|
73
73
|
this._goTo(this.index - 1);
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
+
// Physical arrow keys (horizontal axis only): in RTL the next slide sits
|
|
77
|
+
// to the physical LEFT, so the right arrow must travel toward "previous".
|
|
78
|
+
keyNext() {
|
|
79
|
+
this._rtl() ? this.scrollPrev() : this.scrollNext();
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
keyPrev() {
|
|
83
|
+
this._rtl() ? this.scrollNext() : this.scrollPrev();
|
|
84
|
+
}
|
|
85
|
+
|
|
76
86
|
get slides() {
|
|
77
87
|
return this.track ? Array.from(this.track.children) : [];
|
|
78
88
|
}
|
|
@@ -129,6 +139,8 @@ export default class extends Controller {
|
|
|
129
139
|
const drag = this.drag;
|
|
130
140
|
if (!drag) return;
|
|
131
141
|
drag.delta = this._pointerPos(e) - drag.start;
|
|
142
|
+
// Normalize to scroll-axis space: in RTL a rightward drag reveals next.
|
|
143
|
+
if (this._rtl()) drag.delta = -drag.delta;
|
|
132
144
|
if (!drag.moved && Math.abs(drag.delta) > 5) {
|
|
133
145
|
drag.moved = true;
|
|
134
146
|
this.track.style.transition = "none";
|
|
@@ -183,13 +195,22 @@ export default class extends Controller {
|
|
|
183
195
|
return this.options.axis === "y" ? e.clientY : e.clientX;
|
|
184
196
|
}
|
|
185
197
|
|
|
198
|
+
// In RTL the horizontal track lays slides right-to-left: later slides have
|
|
199
|
+
// SMALLER offsetLeft and the track must translate in +x to reveal them.
|
|
200
|
+
// All geometry below works in a direction-neutral "offset along the scroll
|
|
201
|
+
// axis" space; _rtl() flips the signs at the physical boundaries only.
|
|
202
|
+
_rtl() {
|
|
203
|
+
return this.options.axis !== "y" && getComputedStyle(this.element).direction === "rtl";
|
|
204
|
+
}
|
|
205
|
+
|
|
186
206
|
_offsetOf(index) {
|
|
187
207
|
const slide = this.slides[index];
|
|
188
208
|
const first = this.slides[0];
|
|
189
209
|
if (!slide || !first) return 0;
|
|
190
|
-
|
|
210
|
+
let raw = this.options.axis === "y"
|
|
191
211
|
? slide.offsetTop - first.offsetTop
|
|
192
212
|
: slide.offsetLeft - first.offsetLeft;
|
|
213
|
+
if (this._rtl()) raw = -raw;
|
|
193
214
|
// Never scroll past the content edge: with multi-up layouts the last
|
|
194
215
|
// slides' offsets overshoot the max scrollable offset (track − viewport).
|
|
195
216
|
return Math.min(Math.max(0, raw), this._maxOffset());
|
|
@@ -211,6 +232,6 @@ export default class extends Controller {
|
|
|
211
232
|
_translateTo(offset) {
|
|
212
233
|
this.track.style.transform = this.options.axis === "y"
|
|
213
234
|
? `translate3d(0, ${-offset}px, 0)`
|
|
214
|
-
: `translate3d(${-offset}px, 0, 0)`;
|
|
235
|
+
: `translate3d(${this._rtl() ? offset : -offset}px, 0, 0)`;
|
|
215
236
|
}
|
|
216
237
|
}
|
|
@@ -19,13 +19,19 @@ module PhlexKit
|
|
|
19
19
|
end
|
|
20
20
|
|
|
21
21
|
def view_template(&)
|
|
22
|
-
|
|
22
|
+
base = {
|
|
23
23
|
class: "pk-chart",
|
|
24
|
+
# Canvas content is invisible to AT — announce it as an image with at
|
|
25
|
+
# least a generic name; pass aria: { label: "…" } (or block fallback
|
|
26
|
+
# content) to describe the actual data.
|
|
27
|
+
role: "img",
|
|
24
28
|
data: {
|
|
25
29
|
controller: "phlex-kit--chart",
|
|
26
30
|
phlex_kit__chart_options_value: JSON.generate(@options)
|
|
27
31
|
}
|
|
28
|
-
}
|
|
32
|
+
}
|
|
33
|
+
base[:aria] = { label: "Chart" } unless aria_labelled?
|
|
34
|
+
canvas(**mix(base, @attrs), &)
|
|
29
35
|
end
|
|
30
36
|
end
|
|
31
37
|
end
|
|
@@ -3,19 +3,42 @@ import { Controller } from "@hotwired/stimulus"
|
|
|
3
3
|
// Connects to data-controller="phlex-kit--clipboard". Copies the source text and
|
|
4
4
|
// flashes a success/error popover (CSS-positioned, no @floating-ui). Ported from ruby_ui.
|
|
5
5
|
export default class extends Controller {
|
|
6
|
-
|
|
6
|
+
// No "trigger" target: the click->#copy action alone wires the trigger.
|
|
7
|
+
static targets = ["source", "successPopover", "errorPopover"]
|
|
8
|
+
|
|
9
|
+
connect() {
|
|
10
|
+
// Turbo snapshots at turbo:before-cache, BEFORE disconnect — hide the
|
|
11
|
+
// flash now or a restore shows a permanently stuck "Copied!" popover.
|
|
12
|
+
document.addEventListener("turbo:before-cache", this.beforeCache)
|
|
13
|
+
}
|
|
7
14
|
|
|
8
15
|
disconnect() {
|
|
9
16
|
// Don't leave the 1.5s hide timer running into a dead/recycled element
|
|
10
17
|
// (Turbo can detach and re-attach this subtree mid-flash).
|
|
18
|
+
clearTimeout(this.timer)
|
|
19
|
+
document.removeEventListener("turbo:before-cache", this.beforeCache)
|
|
20
|
+
this.hideAll()
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
beforeCache = () => {
|
|
11
24
|
clearTimeout(this.timer)
|
|
12
25
|
this.hideAll()
|
|
13
26
|
}
|
|
14
27
|
|
|
15
28
|
copy() {
|
|
29
|
+
// A source usually wraps one element (input/code/…), but bare text is
|
|
30
|
+
// legal too — fall back to the source's own text instead of erroring.
|
|
16
31
|
const el = this.sourceTarget.children[0]
|
|
17
|
-
|
|
18
|
-
|
|
32
|
+
// TEXTAREA reads .value like INPUT — innerText would return the initial
|
|
33
|
+
// content, not the user-edited value.
|
|
34
|
+
const text = el
|
|
35
|
+
? (["INPUT", "TEXTAREA"].includes(el.tagName) ? el.value : el.innerText)
|
|
36
|
+
: this.sourceTarget.textContent.trim()
|
|
37
|
+
if (!text) { this.show(this.errorPopoverTarget); return }
|
|
38
|
+
// navigator.clipboard is undefined in insecure contexts (plain-HTTP
|
|
39
|
+
// hosts, some webviews) — that's the error popover's job, not an
|
|
40
|
+
// unhandled TypeError's.
|
|
41
|
+
if (!navigator.clipboard) { this.show(this.errorPopoverTarget); return }
|
|
19
42
|
navigator.clipboard.writeText(text)
|
|
20
43
|
.then(() => this.show(this.successPopoverTarget))
|
|
21
44
|
.catch(() => this.show(this.errorPopoverTarget))
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
module PhlexKit
|
|
2
2
|
class ClipboardTrigger < BaseComponent
|
|
3
3
|
def initialize(**attrs) = (@attrs = attrs)
|
|
4
|
-
def view_template(&) = div(**mix({ class: "pk-clipboard-trigger", data: {
|
|
4
|
+
def view_template(&) = div(**mix({ class: "pk-clipboard-trigger", data: { action: "click->phlex-kit--clipboard#copy" } }, @attrs), &)
|
|
5
5
|
end
|
|
6
6
|
end
|