phlex_kit 0.13.0 → 0.14.1

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 (119) hide show
  1. checksums.yaml +4 -4
  2. data/app/components/phlex_kit/accordion/accordion_content.rb +3 -1
  3. data/app/components/phlex_kit/accordion/accordion_default_trigger.rb +1 -1
  4. data/app/components/phlex_kit/accordion/accordion_trigger.rb +1 -1
  5. data/app/components/phlex_kit/alert/alert.rb +5 -1
  6. data/app/components/phlex_kit/alert_dialog/alert_dialog_controller.js +16 -6
  7. data/app/components/phlex_kit/alert_dialog/alert_dialog_media.rb +4 -1
  8. data/app/components/phlex_kit/aspect_ratio/aspect_ratio.rb +14 -4
  9. data/app/components/phlex_kit/attachment/attachment.css +7 -1
  10. data/app/components/phlex_kit/attachment/scroll_fade_controller.js +4 -3
  11. data/app/components/phlex_kit/avatar/avatar.css +4 -0
  12. data/app/components/phlex_kit/breadcrumb/breadcrumb.css +4 -0
  13. data/app/components/phlex_kit/breadcrumb/breadcrumb.rb +8 -1
  14. data/app/components/phlex_kit/breadcrumb/breadcrumb_page.rb +1 -1
  15. data/app/components/phlex_kit/breadcrumb/breadcrumb_separator.rb +1 -1
  16. data/app/components/phlex_kit/bubble/bubble_content.rb +5 -1
  17. data/app/components/phlex_kit/button_group/button_group_separator.rb +1 -1
  18. data/app/components/phlex_kit/calendar/calendar.css +5 -0
  19. data/app/components/phlex_kit/calendar/calendar_controller.js +26 -8
  20. data/app/components/phlex_kit/calendar/calendar_next.rb +5 -3
  21. data/app/components/phlex_kit/calendar/calendar_prev.rb +5 -3
  22. data/app/components/phlex_kit/carousel/carousel.css +7 -0
  23. data/app/components/phlex_kit/carousel/carousel.rb +8 -4
  24. data/app/components/phlex_kit/carousel/carousel_controller.js +64 -20
  25. data/app/components/phlex_kit/carousel/carousel_item.rb +5 -5
  26. data/app/components/phlex_kit/chart/chart_controller.js +5 -1
  27. data/app/components/phlex_kit/checkbox/checkbox.css +16 -0
  28. data/app/components/phlex_kit/checkbox/checkbox.rb +5 -1
  29. data/app/components/phlex_kit/clipboard/clipboard.rb +7 -0
  30. data/app/components/phlex_kit/clipboard/clipboard_controller.js +12 -1
  31. data/app/components/phlex_kit/clipboard/clipboard_popover.rb +5 -3
  32. data/app/components/phlex_kit/combobox/combobox.rb +1 -1
  33. data/app/components/phlex_kit/combobox/combobox_clear_button.rb +5 -3
  34. data/app/components/phlex_kit/combobox/combobox_controller.js +56 -5
  35. data/app/components/phlex_kit/combobox/combobox_item.rb +7 -4
  36. data/app/components/phlex_kit/command/command_controller.js +9 -1
  37. data/app/components/phlex_kit/command/command_dialog_content.rb +10 -6
  38. data/app/components/phlex_kit/command/command_empty.rb +5 -3
  39. data/app/components/phlex_kit/command/command_separator.rb +1 -1
  40. data/app/components/phlex_kit/context_menu/context_menu_checkbox_item.rb +7 -5
  41. data/app/components/phlex_kit/context_menu/context_menu_content.rb +7 -1
  42. data/app/components/phlex_kit/context_menu/context_menu_controller.js +32 -3
  43. data/app/components/phlex_kit/context_menu/context_menu_group.rb +4 -1
  44. data/app/components/phlex_kit/context_menu/context_menu_item.rb +5 -2
  45. data/app/components/phlex_kit/context_menu/context_menu_radio_group.rb +4 -1
  46. data/app/components/phlex_kit/context_menu/context_menu_radio_item.rb +7 -5
  47. data/app/components/phlex_kit/context_menu/context_menu_separator.rb +6 -1
  48. data/app/components/phlex_kit/context_menu/context_menu_sub_content.rb +4 -1
  49. data/app/components/phlex_kit/context_menu/context_menu_sub_trigger.rb +12 -9
  50. data/app/components/phlex_kit/data_table/data_table.css +4 -0
  51. data/app/components/phlex_kit/data_table/data_table_search_controller.js +15 -1
  52. data/app/components/phlex_kit/data_table/data_table_select_all_checkbox.rb +8 -4
  53. data/app/components/phlex_kit/drawer/drawer.css +4 -0
  54. data/app/components/phlex_kit/dropdown_menu/dropdown_menu_checkbox_item.rb +7 -5
  55. data/app/components/phlex_kit/dropdown_menu/dropdown_menu_content.rb +4 -1
  56. data/app/components/phlex_kit/dropdown_menu/dropdown_menu_controller.js +25 -2
  57. data/app/components/phlex_kit/dropdown_menu/dropdown_menu_group.rb +4 -1
  58. data/app/components/phlex_kit/dropdown_menu/dropdown_menu_item.rb +3 -2
  59. data/app/components/phlex_kit/dropdown_menu/dropdown_menu_radio_group.rb +4 -1
  60. data/app/components/phlex_kit/dropdown_menu/dropdown_menu_radio_item.rb +7 -5
  61. data/app/components/phlex_kit/dropdown_menu/dropdown_menu_separator.rb +6 -1
  62. data/app/components/phlex_kit/dropdown_menu/dropdown_menu_sub_content.rb +4 -1
  63. data/app/components/phlex_kit/dropdown_menu/dropdown_menu_sub_trigger.rb +12 -9
  64. data/app/components/phlex_kit/empty/empty.rb +1 -0
  65. data/app/components/phlex_kit/field/field.rb +4 -1
  66. data/app/components/phlex_kit/field/field_error.rb +4 -1
  67. data/app/components/phlex_kit/form_field/form_field_controller.js +22 -5
  68. data/app/components/phlex_kit/icon/icon.rb +5 -1
  69. data/app/components/phlex_kit/input_group/input_group.css +6 -3
  70. data/app/components/phlex_kit/input_otp/input_otp_controller.js +14 -6
  71. data/app/components/phlex_kit/input_otp/input_otp_slot.rb +7 -6
  72. data/app/components/phlex_kit/item/item.css +9 -1
  73. data/app/components/phlex_kit/item/item_separator.rb +1 -1
  74. data/app/components/phlex_kit/label/label.css +6 -1
  75. data/app/components/phlex_kit/menubar/menubar.rb +5 -3
  76. data/app/components/phlex_kit/menubar/menubar_checkbox_item.rb +6 -6
  77. data/app/components/phlex_kit/menubar/menubar_content.rb +5 -1
  78. data/app/components/phlex_kit/menubar/menubar_controller.js +28 -2
  79. data/app/components/phlex_kit/menubar/menubar_group.rb +4 -1
  80. data/app/components/phlex_kit/menubar/menubar_item.rb +4 -3
  81. data/app/components/phlex_kit/menubar/menubar_menu.rb +4 -1
  82. data/app/components/phlex_kit/menubar/menubar_radio_group.rb +4 -1
  83. data/app/components/phlex_kit/menubar/menubar_radio_item.rb +6 -6
  84. data/app/components/phlex_kit/menubar/menubar_separator.rb +6 -1
  85. data/app/components/phlex_kit/menubar/menubar_sub_content.rb +4 -1
  86. data/app/components/phlex_kit/menubar/menubar_sub_trigger.rb +12 -11
  87. data/app/components/phlex_kit/menubar/menubar_trigger.rb +7 -4
  88. data/app/components/phlex_kit/message_scroller/message_scroller_controller.js +5 -0
  89. data/app/components/phlex_kit/pagination/pagination.css +4 -0
  90. data/app/components/phlex_kit/popover/popover_content.rb +8 -1
  91. data/app/components/phlex_kit/popover/popover_controller.js +4 -0
  92. data/app/components/phlex_kit/radio_button/radio_button.css +16 -0
  93. data/app/components/phlex_kit/radio_button/radio_button.rb +1 -1
  94. data/app/components/phlex_kit/resizable/resizable_controller.js +17 -4
  95. data/app/components/phlex_kit/resizable/resizable_handle.rb +9 -7
  96. data/app/components/phlex_kit/select/select.rb +1 -1
  97. data/app/components/phlex_kit/select/select_content.rb +7 -5
  98. data/app/components/phlex_kit/select/select_controller.js +42 -0
  99. data/app/components/phlex_kit/select/select_separator.rb +1 -1
  100. data/app/components/phlex_kit/sheet/sheet_content_controller.js +12 -5
  101. data/app/components/phlex_kit/sidebar/sidebar_group_action.rb +1 -1
  102. data/app/components/phlex_kit/sidebar/sidebar_menu_action.rb +1 -1
  103. data/app/components/phlex_kit/sidebar/sidebar_rail.rb +3 -2
  104. data/app/components/phlex_kit/sidebar/sidebar_trigger.rb +2 -1
  105. data/app/components/phlex_kit/switch/switch.rb +4 -1
  106. data/app/components/phlex_kit/table/table_head.rb +6 -1
  107. data/app/components/phlex_kit/tabs/tabs_content.rb +6 -1
  108. data/app/components/phlex_kit/tabs/tabs_trigger.rb +3 -1
  109. data/app/components/phlex_kit/theme_toggle/theme_toggle_controller.js +24 -6
  110. data/app/components/phlex_kit/toast/toast_controller.js +46 -1
  111. data/app/components/phlex_kit/toast/toaster_controller.js +29 -1
  112. data/app/components/phlex_kit/toggle/toggle.rb +1 -1
  113. data/app/components/phlex_kit/toggle_group/toggle_group.rb +19 -4
  114. data/app/components/phlex_kit/toggle_group/toggle_group_controller.js +6 -1
  115. data/app/components/phlex_kit/toggle_group/toggle_group_item.rb +2 -2
  116. data/app/components/phlex_kit/tooltip/tooltip_controller.js +5 -0
  117. data/app/components/phlex_kit/typography/typography.css +5 -2
  118. data/lib/phlex_kit/version.rb +1 -1
  119. metadata +1 -1
@@ -80,9 +80,17 @@ export default class extends Controller {
80
80
  this._onWindowDismissId = (e) => this._dismissById(e.detail?.id)
81
81
  this._onKey = this._onKey.bind(this)
82
82
 
83
+ // The region is data-turbo-permanent (in-flight toasts survive a Turbo
84
+ // Drive visit), but that means Turbo KEEPS this old region and DISCARDS
85
+ // the incoming page's — throwing away any server-rendered flash toasts it
86
+ // holds. Adopt them into this permanent list before the transplant so
87
+ // flash: works on Turbo Drive visits, not only full page loads.
88
+ this._onBeforeRender = (e) => this._adoptIncomingToasts(e)
89
+
83
90
  window.addEventListener("phlex-kit:toast", this._onWindowToast)
84
91
  window.addEventListener("phlex-kit:toast:dismiss-all", this._onWindowDismissAll)
85
92
  window.addEventListener("phlex-kit:toast:dismiss", this._onWindowDismissId)
93
+ document.addEventListener("turbo:before-render", this._onBeforeRender)
86
94
  this._listEl.addEventListener("pointerenter", this._onPointerEnter)
87
95
  this._listEl.addEventListener("pointerleave", this._onPointerLeave)
88
96
  document.addEventListener("keydown", this._onKey)
@@ -96,6 +104,7 @@ export default class extends Controller {
96
104
  window.removeEventListener("phlex-kit:toast", this._onWindowToast)
97
105
  window.removeEventListener("phlex-kit:toast:dismiss-all", this._onWindowDismissAll)
98
106
  window.removeEventListener("phlex-kit:toast:dismiss", this._onWindowDismissId)
107
+ document.removeEventListener("turbo:before-render", this._onBeforeRender)
99
108
  this._listEl?.removeEventListener("pointerenter", this._onPointerEnter)
100
109
  this._listEl?.removeEventListener("pointerleave", this._onPointerLeave)
101
110
  document.removeEventListener("keydown", this._onKey)
@@ -170,7 +179,10 @@ export default class extends Controller {
170
179
  node.appendChild(btn)
171
180
  }
172
181
 
173
- if (detail.closeButton && this.hasCloseTplTarget) {
182
+ // Don't append a second close button when the skeleton already baked one
183
+ // in (region rendered with close_button: true) — spawning with
184
+ // closeButton: true would otherwise stack two ×'s.
185
+ if (detail.closeButton && this.hasCloseTplTarget && !node.querySelector('[data-slot="close"]')) {
174
186
  const x = this._cloneSlot(this.closeTplTarget)
175
187
  node.classList.add("pk-toast-with-close")
176
188
  node.appendChild(x)
@@ -180,6 +192,22 @@ export default class extends Controller {
180
192
  return node.id
181
193
  }
182
194
 
195
+ // Move server-rendered flash toasts from the incoming (about-to-be-discarded)
196
+ // page's matching region into this permanent list, so a Turbo Drive visit
197
+ // doesn't silently drop them. Runs on turbo:before-render, before Turbo
198
+ // transplants this permanent element into the new body (children ride along).
199
+ _adoptIncomingToasts(e) {
200
+ const newBody = e.detail?.newBody
201
+ if (!newBody || !this._listEl?.id) return
202
+ const incomingList = newBody.querySelector(`#${CSS.escape(this._listEl.id)}`)
203
+ if (!incomingList) return
204
+ incomingList.querySelectorAll(":scope > [data-phlex-kit--toaster-target='toast']").forEach((el) => {
205
+ // Skip a flash id already showing here, so a re-render doesn't dupe it.
206
+ if (el.id && this._listEl.querySelector(`#${CSS.escape(el.id)}`)) return
207
+ this._listEl.appendChild(el.cloneNode(true))
208
+ })
209
+ }
210
+
183
211
  // True when this region should act on a toast event carrying `region`
184
212
  // (the list's base id or the wrapper's "<id>-region" both match). Without
185
213
  // an explicit region, only the page's first region in DOM order handles
@@ -39,7 +39,7 @@ module PhlexKit
39
39
  end
40
40
 
41
41
  def button_default_attrs
42
- a = { type: "button", class: classes, aria: { pressed: @pressed.to_s },
42
+ a = { type: :button, class: classes, aria: { pressed: @pressed.to_s },
43
43
  data: { state: @pressed ? "on" : "off", phlex_kit__toggle_target: "button" } }
44
44
  a[:disabled] = true if @disabled
45
45
  a
@@ -42,9 +42,16 @@ module PhlexKit
42
42
  # (`pressed || enabledItems()[0]`). Items call this once each, in render
43
43
  # order, as they're constructed — a single-threaded sequential claim, not
44
44
  # a lookahead over siblings not yet rendered.
45
- def claim_tab_stop(item_disabled)
46
- return false unless selected_values.empty?
45
+ def claim_tab_stop(item_disabled, pressed: false)
46
+ # A pressed-but-disabled item can't take focus (reconcile() mirrors
47
+ # this with `pressed && !el.disabled`): unlock the fallback claim so a
48
+ # LATER enabled item takes the stop instead of the whole group
49
+ # rendering Tab-unreachable. (If every enabled item precedes the
50
+ # pressed-disabled one, the pre-JS render still has no stop — the
51
+ # sequential claim can't look ahead; reconcile() fixes it at connect.)
52
+ @selection_unfocusable = true if pressed && item_disabled
47
53
  return false if item_disabled
54
+ return false unless selected_values.empty? || @selection_unfocusable
48
55
  return false if @tab_stop_claimed
49
56
  @tab_stop_claimed = true
50
57
  true
@@ -61,10 +68,17 @@ module PhlexKit
61
68
 
62
69
  def render_hidden_inputs
63
70
  return unless @name
71
+ # Disabled in lockstep with the group — a disabled toggle group must
72
+ # not submit its value (matches toggle.rb and native form semantics).
73
+ disabled = @disabled ? true : nil
64
74
  if @type == :single
65
- input(type: "hidden", name: @name, value: selected_values.first.to_s, data: { phlex_kit__toggle_group_target: "input" })
75
+ input(type: "hidden", name: @name, value: selected_values.first.to_s, disabled: disabled,
76
+ data: { phlex_kit__toggle_group_target: "input" })
66
77
  else
67
- selected_values.each { |v| input(type: "hidden", name: "#{@name}[]", value: v, data: { phlex_kit__toggle_group_target: "input" }) }
78
+ selected_values.each do |v|
79
+ input(type: "hidden", name: "#{@name}[]", value: v, disabled: disabled,
80
+ data: { phlex_kit__toggle_group_target: "input" })
81
+ end
68
82
  end
69
83
  end
70
84
 
@@ -74,6 +88,7 @@ module PhlexKit
74
88
  data: { controller: "phlex-kit--toggle-group",
75
89
  phlex_kit__toggle_group_type_value: @type.to_s,
76
90
  phlex_kit__toggle_group_name_value: @name.to_s,
91
+ phlex_kit__toggle_group_disabled_value: @disabled.to_s,
77
92
  orientation: @orientation.to_s, spacing: @spacing.to_s } }
78
93
  # spacing: N gaps the items by N × .25rem (Tailwind-style scale) via the
79
94
  # custom property toggle_group.css reads. Trailing ";" so a caller
@@ -3,7 +3,7 @@ import { Controller } from "@hotwired/stimulus"
3
3
  // Connects to data-controller="phlex-kit--toggle-group"
4
4
  export default class extends Controller {
5
5
  static targets = ["item", "input"]
6
- static values = { type: String, name: String }
6
+ static values = { type: String, name: String, disabled: Boolean }
7
7
 
8
8
  connect() { this.reconcile() }
9
9
 
@@ -100,6 +100,11 @@ export default class extends Controller {
100
100
  input.type = "hidden"
101
101
  input.name = name
102
102
  input.value = value
103
+ // A disabled group's hidden input is server-rendered `disabled` so it
104
+ // won't submit (toggle_group.rb). rebuildInputs() removes that server
105
+ // input and recreates it here on connect() — carry the disabled flag or
106
+ // the group would start submitting its value post-hydration.
107
+ if (this.disabledValue) input.disabled = true
103
108
  input.setAttribute("data-phlex-kit--toggle-group-target", "input")
104
109
  return input
105
110
  }
@@ -17,7 +17,7 @@ module PhlexKit
17
17
  # group_context[:group] is nil for a hand-built context (bypassing
18
18
  # ToggleGroup#item_context) — treat that as "no tab-stop claim" rather
19
19
  # than raising, since standalone tests construct contexts by hand.
20
- @is_first_tab_stop = group_context[:group]&.claim_tab_stop(item_disabled) || false
20
+ @is_first_tab_stop = group_context[:group]&.claim_tab_stop(item_disabled, pressed: pressed) || false
21
21
  super(pressed: pressed, name: nil, value: @item_value,
22
22
  variant: variant || group_context[:variant],
23
23
  size: size || group_context[:size],
@@ -31,7 +31,7 @@ module PhlexKit
31
31
  private
32
32
 
33
33
  def item_default_attrs
34
- a = { type: "button",
34
+ a = { type: :button,
35
35
  class: ([ "pk-toggle" ] + self.class.modifier_classes(variant: @variant, size: @size) + [ "pk-toggle-group-item" ]).join(" "),
36
36
  data: { state: @pressed ? "on" : "off", value: @item_value,
37
37
  phlex_kit__toggle_group_target: "item",
@@ -20,6 +20,11 @@ export default class extends Controller {
20
20
  })
21
21
  }
22
22
 
23
+ // connect() re-derives reflected state from live truth: a Turbo snapshot
24
+ // serializes an Escape-dismissed suppression, and a cache restore must
25
+ // not resurrect it (the pointer/focus that justified it is gone).
26
+ delete this.element.dataset.pkDismissed
27
+
23
28
  this._onKeydown = (e) => {
24
29
  if (e.key !== "Escape") return
25
30
  if (this.element.matches(":hover, :focus-within")) this.element.dataset.pkDismissed = ""
@@ -50,6 +50,9 @@
50
50
  /* Blockquote */
51
51
  .pk-blockquote { margin: 1.5rem 0; border-inline-start: 2px solid var(--pk-border); padding-inline-start: 1.5rem; font-style: italic; }
52
52
 
53
- /* Prose list (shadcn typography's list style). */
54
- .pk-list { margin: 1.5rem 0; padding-inline-start: 1.5rem; list-style: disc; }
53
+ /* Prose list (shadcn typography's list style). Ordered variant (as: :ol)
54
+ keeps its numbers the disc is scoped to unordered lists. */
55
+ .pk-list { margin: 1.5rem 0; padding-inline-start: 1.5rem; }
56
+ ul.pk-list { list-style: disc; }
57
+ ol.pk-list { list-style: decimal; }
55
58
  .pk-list > li { margin-top: .5rem; }
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module PhlexKit
4
- VERSION = "0.13.0"
4
+ VERSION = "0.14.1"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: phlex_kit
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.13.0
4
+ version: 0.14.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Matt Kennedy