kiso 0.5.2.pre → 0.6.0.pre

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 (122) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/tailwind/kiso/button.css +12 -3
  3. data/app/assets/tailwind/kiso/checkbox.css +13 -2
  4. data/app/assets/tailwind/kiso/color-mode.css +15 -3
  5. data/app/assets/tailwind/kiso/dashboard.css +97 -44
  6. data/app/assets/tailwind/kiso/dialog.css +39 -5
  7. data/app/assets/tailwind/kiso/engine.css +117 -34
  8. data/app/assets/tailwind/kiso/input-otp.css +24 -4
  9. data/app/assets/tailwind/kiso/palettes/blue.css +14 -5
  10. data/app/assets/tailwind/kiso/palettes/green.css +9 -5
  11. data/app/assets/tailwind/kiso/palettes/orange.css +9 -5
  12. data/app/assets/tailwind/kiso/palettes/violet.css +9 -5
  13. data/app/assets/tailwind/kiso/palettes/zinc.css +11 -7
  14. data/app/assets/tailwind/kiso/radio-group.css +11 -4
  15. data/app/assets/tailwind/kiso/slider.css +25 -6
  16. data/app/assets/tailwind/kiso/tooltip.css +37 -11
  17. data/app/helpers/kiso/app_component_helper.rb +83 -34
  18. data/app/helpers/kiso/component_helper.rb +227 -70
  19. data/app/helpers/kiso/icon_helper.rb +101 -39
  20. data/app/helpers/kiso/theme_helper.rb +50 -9
  21. data/app/helpers/kiso/ui_context_helper.rb +87 -35
  22. data/app/javascript/controllers/kiso/combobox_controller.js +10 -2
  23. data/app/javascript/controllers/kiso/command_controller.js +2 -0
  24. data/app/javascript/controllers/kiso/command_dialog_controller.js +4 -0
  25. data/app/javascript/controllers/kiso/dialog_controller.js +6 -1
  26. data/app/javascript/controllers/kiso/dialog_trigger_controller.js +1 -1
  27. data/app/javascript/controllers/kiso/dropdown_menu_controller.js +23 -5
  28. data/app/javascript/controllers/kiso/index.js +25 -0
  29. data/app/javascript/controllers/kiso/input_otp_controller.js +5 -3
  30. data/app/javascript/controllers/kiso/popover_controller.js +18 -4
  31. data/app/javascript/controllers/kiso/select_controller.js +10 -2
  32. data/app/javascript/controllers/kiso/sidebar_controller.js +26 -4
  33. data/app/javascript/controllers/kiso/slider_controller.js +3 -3
  34. data/app/javascript/controllers/kiso/theme_controller.js +2 -1
  35. data/app/javascript/controllers/kiso/toggle_controller.js +2 -0
  36. data/app/javascript/controllers/kiso/toggle_group_controller.js +3 -0
  37. data/app/javascript/controllers/kiso/tooltip_controller.js +3 -0
  38. data/app/javascript/kiso/utils/focusable.js +14 -0
  39. data/app/javascript/kiso/utils/highlight.js +15 -1
  40. data/app/views/kiso/components/_alert.html.erb +2 -0
  41. data/app/views/kiso/components/_alert_dialog.html.erb +5 -2
  42. data/app/views/kiso/components/_app.html.erb +2 -0
  43. data/app/views/kiso/components/_aspect_ratio.html.erb +1 -0
  44. data/app/views/kiso/components/_avatar.html.erb +6 -2
  45. data/app/views/kiso/components/_button.html.erb +3 -0
  46. data/app/views/kiso/components/_checkbox.html.erb +1 -0
  47. data/app/views/kiso/components/_color_mode_button.html.erb +2 -0
  48. data/app/views/kiso/components/_color_mode_select.html.erb +2 -0
  49. data/app/views/kiso/components/_combobox.html.erb +3 -0
  50. data/app/views/kiso/components/_command.html.erb +2 -0
  51. data/app/views/kiso/components/_dashboard_group.html.erb +4 -0
  52. data/app/views/kiso/components/_dashboard_navbar.html.erb +2 -0
  53. data/app/views/kiso/components/_dashboard_panel.html.erb +1 -0
  54. data/app/views/kiso/components/_dashboard_sidebar.html.erb +2 -0
  55. data/app/views/kiso/components/_dashboard_toolbar.html.erb +2 -0
  56. data/app/views/kiso/components/_dialog.html.erb +3 -0
  57. data/app/views/kiso/components/_dropdown_menu.html.erb +2 -0
  58. data/app/views/kiso/components/_empty.html.erb +2 -0
  59. data/app/views/kiso/components/_field.html.erb +2 -0
  60. data/app/views/kiso/components/_field_group.html.erb +1 -0
  61. data/app/views/kiso/components/_field_set.html.erb +1 -0
  62. data/app/views/kiso/components/_input_group.html.erb +1 -0
  63. data/app/views/kiso/components/_input_otp.html.erb +3 -0
  64. data/app/views/kiso/components/_nav.html.erb +2 -0
  65. data/app/views/kiso/components/_page_card.html.erb +3 -0
  66. data/app/views/kiso/components/_page_header.html.erb +3 -0
  67. data/app/views/kiso/components/_page_section.html.erb +2 -0
  68. data/app/views/kiso/components/_pagination.html.erb +2 -0
  69. data/app/views/kiso/components/_popover.html.erb +3 -0
  70. data/app/views/kiso/components/_select.html.erb +3 -0
  71. data/app/views/kiso/components/_select_native.html.erb +2 -0
  72. data/app/views/kiso/components/_separator.html.erb +2 -0
  73. data/app/views/kiso/components/_skeleton.html.erb +1 -0
  74. data/app/views/kiso/components/_slider.html.erb +4 -0
  75. data/app/views/kiso/components/_spinner.html.erb +2 -0
  76. data/app/views/kiso/components/_stats_card.html.erb +2 -0
  77. data/app/views/kiso/components/_stats_grid.html.erb +1 -0
  78. data/app/views/kiso/components/_switch.html.erb +2 -0
  79. data/app/views/kiso/components/_table.html.erb +2 -0
  80. data/app/views/kiso/components/_textarea.html.erb +3 -0
  81. data/app/views/kiso/components/_toggle.html.erb +2 -0
  82. data/app/views/kiso/components/_toggle_group.html.erb +2 -0
  83. data/app/views/kiso/components/_tooltip.html.erb +3 -0
  84. data/app/views/kiso/components/alert_dialog/_action.html.erb +1 -0
  85. data/app/views/kiso/components/alert_dialog/_cancel.html.erb +1 -0
  86. data/app/views/kiso/components/alert_dialog/_description.html.erb +1 -0
  87. data/app/views/kiso/components/alert_dialog/_title.html.erb +1 -0
  88. data/app/views/kiso/components/avatar/_image.html.erb +1 -0
  89. data/app/views/kiso/components/breadcrumb/_separator.html.erb +3 -0
  90. data/app/views/kiso/components/combobox/_chips.html.erb +3 -0
  91. data/app/views/kiso/components/command/_dialog.html.erb +2 -0
  92. data/app/views/kiso/components/dashboard_sidebar/_collapse.html.erb +2 -0
  93. data/app/views/kiso/components/dialog/_close.html.erb +1 -0
  94. data/app/views/kiso/components/field/_error.html.erb +4 -0
  95. data/app/views/kiso/components/field/_label.html.erb +2 -0
  96. data/app/views/kiso/components/field/_separator.html.erb +3 -0
  97. data/app/views/kiso/components/input_otp/_separator.html.erb +2 -0
  98. data/app/views/kiso/components/input_otp/_slot.html.erb +2 -0
  99. data/app/views/kiso/components/nav/_section.html.erb +4 -0
  100. data/app/views/kiso/components/tooltip/_content.html.erb +2 -0
  101. data/lib/generators/kiso/install/USAGE +23 -0
  102. data/lib/generators/kiso/install/install_generator.rb +91 -0
  103. data/lib/generators/kiso/install/templates/design_system.md.tt +190 -0
  104. data/lib/generators/kiso/install/templates/initializer.rb.tt +40 -0
  105. data/lib/kiso/cli/make.rb +6 -3
  106. data/lib/kiso/cli.rb +10 -0
  107. data/lib/kiso/color_utils.rb +31 -8
  108. data/lib/kiso/configuration.rb +11 -0
  109. data/lib/kiso/engine.rb +9 -2
  110. data/lib/kiso/propshaft_tailwind_stub_filter.rb +9 -2
  111. data/lib/kiso/themes/avatar.rb +40 -6
  112. data/lib/kiso/themes/badge.rb +5 -1
  113. data/lib/kiso/themes/color_mode_button.rb +11 -0
  114. data/lib/kiso/themes/color_mode_select.rb +7 -0
  115. data/lib/kiso/themes/dashboard.rb +28 -0
  116. data/lib/kiso/themes/dropdown_menu.rb +2 -2
  117. data/lib/kiso/themes/input_otp.rb +6 -3
  118. data/lib/kiso/themes/nav.rb +17 -0
  119. data/lib/kiso/themes/pagination.rb +9 -4
  120. data/lib/kiso/themes/shared.rb +27 -7
  121. data/lib/kiso/version.rb +5 -2
  122. metadata +5 -1
@@ -3,13 +3,36 @@
3
3
  require "tailwind_merge"
4
4
 
5
5
  module Kiso
6
- # View helpers for rendering inline SVG icons.
6
+ # View helpers for rendering inline SVG icons with Tailwind classes.
7
7
  #
8
- # Included in all views automatically by {Engine}.
9
- # Delegates to +kiso_icon_tag+ (provided by the kiso-icons gem) for
10
- # actual SVG rendering.
8
+ # Provides two public methods:
9
+ #
10
+ # - {#kiso_icon} -- renders any icon by name. Used in application code
11
+ # and templates when the caller knows the exact icon they want.
12
+ # - {#kiso_component_icon} -- renders a semantic/configurable icon. Used
13
+ # inside Kiso component partials for default icons (close button X,
14
+ # separator chevrons, pagination arrows) so host apps can swap them
15
+ # globally via {Kiso::Configuration#icons}.
16
+ #
17
+ # Both methods delegate to +kiso_icon_tag+ (provided by the kiso-icons
18
+ # gem) for actual SVG rendering. Classes are deduplicated and resolved
19
+ # via +TailwindMerge::Merger+.
20
+ #
21
+ # == Icon sizing
22
+ #
23
+ # By default, icons render without an explicit size class. This allows
24
+ # parent components (Button, Alert, Badge, etc.) to control icon sizing
25
+ # via CSS selectors like +[&_svg]:size-4+. Pass an explicit +size:+
26
+ # preset for standalone icons outside of components.
27
+ #
28
+ # Included in all views automatically by {Kiso::Engine}.
29
+ #
30
+ # @see Kiso::Configuration#icons the global icon name registry
11
31
  module IconHelper
12
- # @return [Hash{Symbol => String}] maps size presets to Tailwind size utilities
32
+ # Maps size preset symbols to Tailwind size utility classes.
33
+ # These follow Kiso's spatial scale for icon sizing.
34
+ #
35
+ # @return [Hash{Symbol => String}]
13
36
  SIZE_PRESETS = {
14
37
  xs: "size-3",
15
38
  sm: "size-4",
@@ -18,23 +41,39 @@ module Kiso
18
41
  xl: "size-8"
19
42
  }.freeze
20
43
 
21
- # @return [String] base Tailwind classes applied to every icon
44
+ # Base Tailwind classes applied to every icon. +shrink-0+ prevents
45
+ # icons from collapsing in flex containers.
46
+ #
47
+ # @return [String]
22
48
  BASE_CLASSES = "shrink-0"
23
49
 
24
- # Renders a configurable component icon.
50
+ # Renders a configurable component icon by semantic name.
51
+ #
52
+ # Component partials call this instead of {#kiso_icon} when the icon
53
+ # should be overridable by host apps. The semantic name is resolved to
54
+ # an actual icon identifier via {Kiso::Configuration#icons}, which host
55
+ # apps can customize in an initializer.
25
56
  #
26
- # Components call this instead of {#kiso_icon} when the icon name should
27
- # be overridable by host apps via {Configuration#icons Kiso.config.icons}.
57
+ # @param semantic_name [Symbol] a key from the icon registry
58
+ # (e.g. +:chevron_right+, +:check+, +:x+, +:spinner+). See
59
+ # {Kiso::Configuration} for the full list of registered names.
60
+ # @param options [Hash] forwarded to {#kiso_icon} (e.g. +size:+,
61
+ # +class:+, +aria:+).
62
+ # @return [ActiveSupport::SafeBuffer] rendered inline SVG
63
+ # @raise [KeyError] if +semantic_name+ is not registered in
64
+ # {Kiso::Configuration#icons}
28
65
  #
29
- # @param semantic_name [Symbol] a key from {Configuration#icons}
30
- # (e.g. +:chevron_right+, +:check+, +:x+)
31
- # @param options [Hash] forwarded to {#kiso_icon}
32
- # @return [ActiveSupport::SafeBuffer] rendered SVG
33
- # @raise [KeyError] if +semantic_name+ is not registered
66
+ # @example In a component partial (separator chevron)
67
+ # <%= kiso_component_icon(:chevron_right, class: "size-3.5") %>
34
68
  #
35
- # @example
36
- # kiso_component_icon(:chevron_right, class: "size-3.5")
37
- # kiso_component_icon(:ellipsis, size: :sm)
69
+ # @example In a component partial (dropdown indicator)
70
+ # <%= kiso_component_icon(:chevron_down, size: :sm) %>
71
+ #
72
+ # @example Host app overrides the icon globally
73
+ # # config/initializers/kiso.rb
74
+ # Kiso.configure do |config|
75
+ # config.icons[:chevron_right] = "heroicons:chevron-right"
76
+ # end
38
77
  def kiso_component_icon(semantic_name, **)
39
78
  icon_name = Kiso.config.icons.fetch(semantic_name) {
40
79
  raise KeyError, "Unknown Kiso icon: #{semantic_name.inspect}. " \
@@ -45,26 +84,41 @@ module Kiso
45
84
 
46
85
  # Renders an inline SVG icon with Tailwind classes.
47
86
  #
48
- # Size defaults to +nil+ so parent components (Button, Alert, Badge) can
49
- # control icon sizing via CSS selectors like +[&_svg]:size-4+.
50
- # Pass an explicit +size:+ for standalone icons outside components.
87
+ # This is the general-purpose icon helper for use in application
88
+ # templates and views. Inside Kiso component partials, prefer
89
+ # {#kiso_component_icon} for default icons so they remain configurable.
90
+ #
91
+ # The +size:+ parameter defaults to +nil+ so parent components (Button,
92
+ # Alert, Badge) can control icon sizing via CSS selectors like
93
+ # +[&_svg]:size-4+. Pass an explicit +size:+ only for standalone icons
94
+ # rendered outside of a Kiso component.
51
95
  #
52
- # @param name [String] icon identifier, optionally prefixed with set
53
- # (e.g. +"check"+, +"lucide:check"+)
96
+ # @param name [String] icon identifier, optionally prefixed with the
97
+ # icon set name separated by a colon (e.g. +"check"+,
98
+ # +"lucide:check"+, +"heroicons:arrow-right"+). Without a prefix,
99
+ # the default icon set is used.
54
100
  # @param size [Symbol, nil] size preset from {SIZE_PRESETS}
55
- # (+:xs+, +:sm+, +:md+, +:lg+, +:xl+)
56
- # @param options [Hash] forwarded to +kiso_icon_tag+
57
- # (e.g. +class:+, +aria:+, +data:+)
58
- # @return [ActiveSupport::SafeBuffer] rendered SVG
59
- #
60
- # @example Basic usage
61
- # kiso_icon("check")
62
- # @example With size preset
63
- # kiso_icon("check", size: :md)
64
- # @example With extra classes
65
- # kiso_icon("check", class: "text-success")
66
- # @example Accessible icon
67
- # kiso_icon("check", aria: { label: "Done" })
101
+ # (+:xs+ = 12px, +:sm+ = 16px, +:md+ = 20px, +:lg+ = 24px,
102
+ # +:xl+ = 32px). Pass +nil+ (default) to let the parent component
103
+ # control sizing.
104
+ # @param options [Hash] forwarded to +kiso_icon_tag+ from the
105
+ # kiso-icons gem (e.g. +class:+, +aria:+, +data:+, +title:+).
106
+ # @return [ActiveSupport::SafeBuffer] rendered inline SVG element
107
+ #
108
+ # @example Basic usage in a template
109
+ # <%= kiso_icon("check") %>
110
+ #
111
+ # @example With a size preset
112
+ # <%= kiso_icon("arrow-right", size: :md) %>
113
+ #
114
+ # @example With extra Tailwind classes
115
+ # <%= kiso_icon("check", class: "text-success") %>
116
+ #
117
+ # @example With accessibility label
118
+ # <%= kiso_icon("check", aria: { label: "Done" }) %>
119
+ #
120
+ # @example From a specific icon set
121
+ # <%= kiso_icon("heroicons:check-circle", size: :lg) %>
68
122
  def kiso_icon(name, size: nil, **options)
69
123
  css_classes = options.delete(:class) || ""
70
124
  size_class = size ? SIZE_PRESETS.fetch(size) : nil
@@ -75,16 +129,24 @@ module Kiso
75
129
 
76
130
  private
77
131
 
78
- # Merges icon class parts via TailwindMerge, filtering out blanks.
132
+ # Merges icon class parts via +TailwindMerge+, filtering out blanks.
79
133
  #
80
- # @param parts [Array<String, nil>] class strings to merge
81
- # @return [String] deduplicated class string
134
+ # When multiple parts specify conflicting Tailwind utilities (e.g.
135
+ # +size-4+ from a preset and +size-6+ from a caller), the last one
136
+ # wins -- the same behavior as +tailwind_merge+ in theme rendering.
137
+ #
138
+ # @param parts [Array<String, nil>] class strings to merge. +nil+ and
139
+ # empty values are filtered out before joining.
140
+ # @return [String] deduplicated, conflict-resolved class string
82
141
  def merge_icon_classes(*parts)
83
142
  combined = parts.reject { |p| p.nil? || p.to_s.empty? }.join(" ")
84
143
  icon_class_merger.merge(combined)
85
144
  end
86
145
 
87
- # @return [TailwindMerge::Merger] memoized merger instance
146
+ # Returns a memoized +TailwindMerge::Merger+ instance for icon class
147
+ # deduplication. Memoized on the view context instance (per-request).
148
+ #
149
+ # @return [TailwindMerge::Merger]
88
150
  def icon_class_merger
89
151
  @icon_class_merger ||= TailwindMerge::Merger.new
90
152
  end
@@ -1,10 +1,35 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Kiso
4
- # View helper for dark mode FOUC prevention.
4
+ # View helper for dark mode FOUC (Flash of Unstyled Content) prevention.
5
5
  #
6
- # Included in all views automatically by {Engine}.
6
+ # Provides {#kiso_theme_script}, which outputs a tiny inline +<script>+
7
+ # that synchronously applies the +.dark+ class to +<html>+ before the
8
+ # browser paints. This eliminates the bright flash that occurs when a
9
+ # dark-mode user loads a page and the stylesheet hasn't applied yet.
10
+ #
11
+ # Kiso's dark mode system uses CSS variable swapping inside a +.dark {}+
12
+ # block (not Tailwind +dark:+ prefixes), so the +.dark+ class on +<html>+
13
+ # is the single toggle that activates the entire dark palette.
14
+ #
15
+ # == Theme resolution order
16
+ #
17
+ # 1. +localStorage.getItem("theme")+ -- fastest, set by the Stimulus
18
+ # +kiso--theme+ controller on toggle.
19
+ # 2. Cookie named +"theme"+ -- fallback for server-side rendering
20
+ # scenarios. Also set by the Stimulus controller.
21
+ # 3. +matchMedia("(prefers-color-scheme: dark)")+ -- OS-level preference
22
+ # when no explicit choice has been stored.
23
+ #
24
+ # Included in all views automatically by {Kiso::Engine}.
25
+ #
26
+ # @see file:app/javascript/controllers/kiso/theme_controller.js
27
+ # the Stimulus controller that handles toggling and persistence
7
28
  module ThemeHelper
29
+ # Minified inline JavaScript that reads the user's theme preference and
30
+ # sets +.dark+ on +<html>+ synchronously. Frozen to prevent mutation.
31
+ #
32
+ # @return [String] minified JavaScript source
8
33
  THEME_SCRIPT = <<~JS.squish.freeze
9
34
  (function(){
10
35
  var k="theme";
@@ -16,21 +41,37 @@ module Kiso
16
41
  })()
17
42
  JS
18
43
 
19
- # Outputs an inline script that prevents dark mode FOUC.
44
+ # Outputs a blocking inline +<script>+ that prevents dark mode FOUC.
20
45
  #
21
- # Reads the theme preference from localStorage (primary) or cookie
22
- # (fallback), respects +prefers-color-scheme+ as default, and sets
23
- # +.dark+ on +<html>+ synchronously before first paint.
46
+ # The script runs synchronously in +<head>+ before stylesheets load,
47
+ # reading the user's stored theme preference and applying +.dark+ to
48
+ # +<html>+ if appropriate. This ensures the first paint uses the correct
49
+ # color scheme.
24
50
  #
25
- # Call in +<head>+, ideally before stylesheet links.
51
+ # The script tag is rendered with +nonce: true+ for Content Security
52
+ # Policy (CSP) compatibility. Rails automatically sets the nonce from
53
+ # +content_security_policy_nonce+.
26
54
  #
27
- # @example In a layout
55
+ # @note Place this call in +<head>+ *before* stylesheet links. The
56
+ # script must execute before the browser encounters CSS that uses
57
+ # the +.dark+ selector, otherwise the first paint will flash.
58
+ #
59
+ # @return [ActiveSupport::SafeBuffer] an HTML +<script>+ tag
60
+ #
61
+ # @example Minimal layout setup
28
62
  # <head>
29
63
  # <%= kiso_theme_script %>
30
64
  # <%= stylesheet_link_tag "tailwind" %>
31
65
  # </head>
32
66
  #
33
- # @return [ActiveSupport::SafeBuffer]
67
+ # @example With Turbo and other head elements
68
+ # <head>
69
+ # <meta charset="utf-8">
70
+ # <%= csrf_meta_tags %>
71
+ # <%= kiso_theme_script %>
72
+ # <%= stylesheet_link_tag "tailwind" %>
73
+ # <%= javascript_importmap_tags %>
74
+ # </head>
34
75
  def kiso_theme_script
35
76
  javascript_tag(THEME_SCRIPT, nonce: true)
36
77
  end
@@ -1,106 +1,158 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Kiso
4
- # Manages request-scoped context stacks for component communication.
4
+ # Manages request-scoped context stacks for parent-to-sub-part
5
+ # communication in composed Kiso components.
5
6
  #
6
- # This is ERB's equivalent of Vue's provide/inject pattern. Two stacks:
7
+ # This is ERB's equivalent of Vue's provide/inject or React's context
8
+ # pattern. It enables parent components to share data with their sub-parts
9
+ # without the caller having to thread locals through every nested call.
7
10
  #
8
- # - **ui:** per-slot CSS class overrides. When a parent component is
9
- # rendered with +ui:+, the hash is pushed onto a stack. Sub-parts read
10
- # their slot override automatically.
11
- # - **scope:** — domain locals shared from parent to sub-parts. When a
12
- # parent is rendered with +scope:+, the hash is pushed onto a stack.
13
- # Sub-parts receive scope values as kwargs, with explicit kwargs
14
- # taking priority. One level deep only — no ancestor resolution.
11
+ # == Two independent stacks
15
12
  #
16
- # Thread-safe because Rails creates a fresh view context (and therefore
17
- # fresh instance variables) per request.
13
+ # - **ui** -- per-slot CSS class overrides. When a parent component is
14
+ # rendered with +ui:+, the hash is pushed onto a stack keyed by
15
+ # component name. Sub-parts read their slot override from the top of
16
+ # the stack automatically (handled by {ComponentHelper#kiso_render_component}).
18
17
  #
19
- # @example ui: stack flow
20
- # # Parent pushes:
18
+ # - **scope** -- domain locals shared from parent to sub-parts. When a
19
+ # parent is rendered with +scope:+, the hash is pushed onto its own
20
+ # stack. Sub-parts receive scope values as kwargs, with explicit kwargs
21
+ # taking priority. One level deep only -- no ancestor resolution.
22
+ #
23
+ # == Thread safety
24
+ #
25
+ # Thread-safe because Rails creates a fresh +ActionView::Base+ instance
26
+ # (and therefore fresh +@kiso_ui_stack+ / +@kiso_scope_stack+ instance
27
+ # variables) for every request. No shared mutable state exists across
28
+ # requests.
29
+ #
30
+ # == Stack vs simple hash
31
+ #
32
+ # A stack (not a simple hash) is necessary because the same component
33
+ # type can be nested. For example, a Card inside a Card must each
34
+ # maintain their own +ui:+ context independently. Push/pop ensures the
35
+ # inner Card's overrides don't leak to the outer Card's sub-parts.
36
+ #
37
+ # @note You should not call these methods directly in application code.
38
+ # They are used internally by {ComponentHelper#kui} and
39
+ # {AppComponentHelper#appui}. The public API for passing context is
40
+ # the +ui:+ and +scope:+ keyword arguments on those helpers.
41
+ #
42
+ # @example ui: context flow (internal)
43
+ # # Parent pushes when rendering:
21
44
  # kiso_push_ui_context(:card, { header: "p-8", title: "text-xl" })
22
45
  #
23
- # # Sub-part reads:
24
- # kiso_current_ui(:card) #=> { header: "p-8", title: "text-xl" }
46
+ # # Sub-part reads its slot override:
47
+ # kiso_current_ui(:card)[:header] #=> "p-8"
25
48
  #
26
- # # After parent renders:
49
+ # # After parent finishes rendering (in ensure block):
27
50
  # kiso_pop_ui_context(:card)
28
51
  #
29
- # @example scope: stack flow
30
- # # Parent pushes:
52
+ # @example scope: context flow (internal)
53
+ # # Parent pushes when rendering:
31
54
  # kiso_push_scope(:room_card, { room: room })
32
55
  #
33
56
  # # Sub-part reads (merged into kwargs before render):
34
57
  # kiso_current_scope(:room_card) #=> { room: room }
35
58
  #
36
- # # After parent renders:
59
+ # # After parent finishes rendering (in ensure block):
37
60
  # kiso_pop_scope(:room_card)
38
61
  #
39
- # @see ComponentHelper#kui
62
+ # @see ComponentHelper#kui the public API that drives these stacks
63
+ # @see AppComponentHelper#appui the host app equivalent
40
64
  module UiContextHelper
41
- # Push a ui hash onto the context stack for a component.
65
+ # Pushes a +ui:+ hash onto the context stack for a component.
66
+ #
67
+ # Called by {ComponentHelper#kiso_render_component} when a parent
68
+ # component is rendered with +ui:+ overrides.
42
69
  #
43
- # @param component [Symbol] the component name (e.g. +:card+)
70
+ # @param component [Symbol] the component name (e.g. +:card+, +:alert+)
44
71
  # @param ui [Hash{Symbol => String}] slot-name to class-string mapping
72
+ # (e.g. +{ header: "p-8", title: "text-xl" }+)
45
73
  # @return [void]
46
74
  def kiso_push_ui_context(component, ui)
47
75
  kiso_ui_stack[component] ||= []
48
76
  kiso_ui_stack[component].push(ui || {})
49
77
  end
50
78
 
51
- # Pop the most recent ui hash for a component.
79
+ # Pops the most recent +ui:+ hash for a component.
80
+ #
81
+ # Called in the +ensure+ block of {ComponentHelper#kiso_render_component}
82
+ # after the parent component finishes rendering, guaranteeing cleanup
83
+ # even if the partial raises an exception.
52
84
  #
53
85
  # @param component [Symbol] the component name
54
- # @return [Hash, nil] the popped ui hash
86
+ # @return [Hash, nil] the popped ui hash, or +nil+ if the stack was empty
55
87
  def kiso_pop_ui_context(component)
56
88
  kiso_ui_stack[component]&.pop
57
89
  end
58
90
 
59
- # Read the current ui hash for a component (top of stack).
91
+ # Reads the current +ui:+ hash for a component (top of stack).
92
+ #
93
+ # Called by {ComponentHelper#kiso_render_component} when rendering a
94
+ # sub-part, to look up the slot override the parent provided.
60
95
  #
61
96
  # @param component [Symbol] the component name
62
- # @return [Hash{Symbol => String}] the current ui overrides, or empty hash
97
+ # @return [Hash{Symbol => String}] the current ui overrides, or an
98
+ # empty hash if no context is active
63
99
  def kiso_current_ui(component)
64
100
  kiso_ui_stack.dig(component, -1) || {}
65
101
  end
66
102
 
67
- # Push a scope hash onto the context stack for a component.
103
+ # Pushes a +scope:+ hash onto the context stack for a component.
68
104
  #
69
- # Scope shares domain locals from a parent component with its sub-parts
70
- # so callers don't have to repeat the same locals on every sub-part call.
105
+ # Scope shares domain locals from a parent component to its sub-parts
106
+ # so callers don't have to repeat the same locals on every nested
107
+ # +kui(:component, :part)+ call.
71
108
  #
72
109
  # @param component [Symbol] the component name (e.g. +:room_card+)
73
110
  # @param scope [Hash] domain locals to share with sub-parts
111
+ # (e.g. +{ room: @room, booking: @booking }+)
74
112
  # @return [void]
75
113
  def kiso_push_scope(component, scope)
76
114
  kiso_scope_stack[component] ||= []
77
115
  kiso_scope_stack[component].push(scope || {})
78
116
  end
79
117
 
80
- # Pop the most recent scope hash for a component.
118
+ # Pops the most recent +scope:+ hash for a component.
119
+ #
120
+ # Called in the +ensure+ block of {ComponentHelper#kiso_render_component}
121
+ # after the parent component finishes rendering.
81
122
  #
82
123
  # @param component [Symbol] the component name
83
- # @return [Hash, nil] the popped scope hash
124
+ # @return [Hash, nil] the popped scope hash, or +nil+ if the stack was empty
84
125
  def kiso_pop_scope(component)
85
126
  kiso_scope_stack[component]&.pop
86
127
  end
87
128
 
88
- # Read the current scope hash for a component (top of stack).
129
+ # Reads the current +scope:+ hash for a component (top of stack).
130
+ #
131
+ # Called by {ComponentHelper#kiso_render_component} when rendering a
132
+ # sub-part. The returned hash is merged into the sub-part's kwargs,
133
+ # with explicit kwargs taking priority over scope values.
89
134
  #
90
135
  # @param component [Symbol] the component name
91
- # @return [Hash] the current scope, or empty hash
136
+ # @return [Hash] the current scope, or an empty hash if no context
137
+ # is active
92
138
  def kiso_current_scope(component)
93
139
  kiso_scope_stack.dig(component, -1) || {}
94
140
  end
95
141
 
96
142
  private
97
143
 
98
- # @return [Hash{Symbol => Array<Hash>}] per-component ui stacks
144
+ # Per-component ui context stacks. Each component name maps to an
145
+ # array of ui hashes, used as a LIFO stack.
146
+ #
147
+ # @return [Hash{Symbol => Array<Hash>}]
99
148
  def kiso_ui_stack
100
149
  @kiso_ui_stack ||= {}
101
150
  end
102
151
 
103
- # @return [Hash{Symbol => Array<Hash>}] per-component scope stacks
152
+ # Per-component scope context stacks. Each component name maps to an
153
+ # array of scope hashes, used as a LIFO stack.
154
+ #
155
+ # @return [Hash{Symbol => Array<Hash>}]
104
156
  def kiso_scope_stack
105
157
  @kiso_scope_stack ||= {}
106
158
  end
@@ -604,12 +604,20 @@ export default class extends Controller {
604
604
  }
605
605
  }
606
606
 
607
- /** @private */
607
+ /**
608
+ * Attaches global listeners for outside-click detection.
609
+ *
610
+ * @private
611
+ */
608
612
  _addGlobalListeners() {
609
613
  document.addEventListener("click", this._handleOutsideClick, true)
610
614
  }
611
615
 
612
- /** @private */
616
+ /**
617
+ * Removes global listeners for outside-click detection.
618
+ *
619
+ * @private
620
+ */
613
621
  _removeGlobalListeners() {
614
622
  document.removeEventListener("click", this._handleOutsideClick, true)
615
623
  }
@@ -29,6 +29,8 @@ import { highlightItem, wrapIndex } from "kiso-ui/utils/highlight"
29
29
  *
30
30
  * @fires kiso--command:select - When an item is selected.
31
31
  * Detail: `{ value: string, item: HTMLElement }`.
32
+ * @fires command:escape - When Escape is pressed in the input. Bubbles up to
33
+ * the parent `kiso--command-dialog` controller for dialog close handling.
32
34
  */
33
35
  export default class extends Controller {
34
36
  static targets = ["input", "list", "empty", "group", "item"]
@@ -4,6 +4,10 @@ import { Controller } from "@hotwired/stimulus"
4
4
  * Command dialog controller. Opens with a configurable keyboard shortcut
5
5
  * (default: Cmd/Ctrl+K) and closes with Escape. Wraps the native `<dialog>` element.
6
6
  *
7
+ * Listens for a custom `command:escape` event bubbled from the nested
8
+ * `kiso--command` controller to close the dialog when Escape is pressed
9
+ * inside the command input.
10
+ *
7
11
  * @example
8
12
  * <dialog data-controller="kiso--command-dialog"
9
13
  * data-kiso--command-dialog-shortcut-value="k"
@@ -84,7 +84,12 @@ export default class extends Controller {
84
84
  }
85
85
 
86
86
  /**
87
- * Responds to Stimulus Value changes for external open/close control.
87
+ * Stimulus value change callback. Responds to changes to `openValue`
88
+ * for external open/close control (e.g., via Turbo morphing or
89
+ * programmatic attribute updates).
90
+ *
91
+ * Skipped during initial `connect()` since that method handles the
92
+ * initial open state directly.
88
93
  */
89
94
  openValueChanged() {
90
95
  // Skip during connect — handled in connect() directly
@@ -64,7 +64,7 @@ export default class extends Controller {
64
64
  * Finds the dialog element and its Stimulus controller, then
65
65
  * calls the provided callback with the controller instance.
66
66
  *
67
- * @param {function} callback - Receives the dialog controller
67
+ * @param {function(Controller): void} callback - Receives the kiso--dialog controller instance
68
68
  * @private
69
69
  */
70
70
  _withDialogController(callback) {
@@ -2,10 +2,20 @@ import { Controller } from "@hotwired/stimulus"
2
2
  import { highlightItem, wrapIndex } from "kiso-ui/utils/highlight"
3
3
  import { startPositioning } from "kiso-ui/utils/positioning"
4
4
 
5
- /** @type {WeakMap<HTMLElement, {enterHandler: Function, leaveHandler: Function}>} */
5
+ /**
6
+ * Tracks mouseenter/mouseleave handlers attached to sub-content elements,
7
+ * enabling cleanup when sub-menus close.
8
+ *
9
+ * @type {WeakMap<HTMLElement, {enterHandler: Function, leaveHandler: Function}>}
10
+ */
6
11
  const _subHandlers = new WeakMap()
7
12
 
8
- /** @type {WeakMap<HTMLElement, Function>} */
13
+ /**
14
+ * Tracks Floating UI position cleanup functions for sub-content elements.
15
+ * Each entry is the cleanup function returned by `startPositioning()`.
16
+ *
17
+ * @type {WeakMap<HTMLElement, Function>}
18
+ */
9
19
  const _subPositionCleanups = new WeakMap()
10
20
 
11
21
  /**
@@ -234,7 +244,7 @@ export default class extends Controller {
234
244
  * Opens a sub-menu on hover, closing sibling sub-menus first.
235
245
  * Cancels any pending close timeout.
236
246
  *
237
- * @param {Event} event - Mouseenter event from a sub-trigger element
247
+ * @param {MouseEvent} event - Mouseenter event from a sub-trigger element
238
248
  */
239
249
  openSubOnHover(event) {
240
250
  if (this._closeSubTimeout) {
@@ -670,13 +680,21 @@ export default class extends Controller {
670
680
  .forEach((el) => el.removeAttribute("data-highlighted"))
671
681
  }
672
682
 
673
- /** @private */
683
+ /**
684
+ * Attaches global listeners for outside-click and keyboard navigation.
685
+ *
686
+ * @private
687
+ */
674
688
  _addGlobalListeners() {
675
689
  document.addEventListener("click", this._handleOutsideClick, true)
676
690
  document.addEventListener("keydown", this._handleKeydown)
677
691
  }
678
692
 
679
- /** @private */
693
+ /**
694
+ * Removes global listeners for outside-click and keyboard navigation.
695
+ *
696
+ * @private
697
+ */
680
698
  _removeGlobalListeners() {
681
699
  document.removeEventListener("click", this._handleOutsideClick, true)
682
700
  document.removeEventListener("keydown", this._handleKeydown)
@@ -1,3 +1,23 @@
1
+ /**
2
+ * Kiso UI Stimulus controller registry.
3
+ *
4
+ * Exports all Kiso Stimulus controllers and provides a `start()` helper
5
+ * to register them with a Stimulus application in one call.
6
+ *
7
+ * @module controllers/kiso
8
+ *
9
+ * @example Using start() to register all controllers at once
10
+ * import KisoUi from "kiso-ui"
11
+ * import { Application } from "@hotwired/stimulus"
12
+ *
13
+ * const application = Application.start()
14
+ * KisoUi.start(application)
15
+ *
16
+ * @example Importing individual controllers
17
+ * import { KisoDialogController } from "kiso-ui"
18
+ * application.register("kiso--dialog", KisoDialogController)
19
+ */
20
+
1
21
  import KisoAlertController from "./alert_controller.js"
2
22
  import KisoComboboxController from "./combobox_controller.js"
3
23
  import KisoCommandController from "./command_controller.js"
@@ -16,6 +36,11 @@ import KisoToggleGroupController from "./toggle_group_controller.js"
16
36
  import KisoTooltipController from "./tooltip_controller.js"
17
37
 
18
38
  const KisoUi = {
39
+ /**
40
+ * Registers all Kiso Stimulus controllers with the given application.
41
+ *
42
+ * @param {import("@hotwired/stimulus").Application} application - The Stimulus application instance
43
+ */
19
44
  start(application) {
20
45
  application.register("kiso--alert", KisoAlertController)
21
46
  application.register("kiso--combobox", KisoComboboxController)
@@ -27,8 +27,8 @@ import { Controller } from "@hotwired/stimulus"
27
27
  *
28
28
  * @property {HTMLInputElement} inputTarget - The transparent real input element
29
29
  * @property {HTMLElement[]} slotTargets - Visual slot div elements
30
- * @property {Number} lengthValue - Expected OTP code length
31
- * @property {String} patternValue - Regex pattern for allowed characters (default: digits only)
30
+ * @property {number} lengthValue - Expected OTP code length (default: 6)
31
+ * @property {string} patternValue - Regex pattern for allowed characters (default: "\\d", digits only)
32
32
  *
33
33
  * @fires kiso--input-otp:change - When the OTP value changes.
34
34
  * Detail: `{ value: string }`
@@ -101,7 +101,9 @@ export default class extends Controller {
101
101
 
102
102
  /**
103
103
  * Re-syncs the active slot after keydown events that may change selection
104
- * (arrow keys, backspace, delete, home, end).
104
+ * (arrow keys, backspace, delete, home, end). Uses `requestAnimationFrame`
105
+ * to wait until the browser has processed the key event and updated
106
+ * `selectionStart`.
105
107
  *
106
108
  * @private
107
109
  */