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.
- checksums.yaml +4 -4
- data/app/assets/tailwind/kiso/button.css +12 -3
- data/app/assets/tailwind/kiso/checkbox.css +13 -2
- data/app/assets/tailwind/kiso/color-mode.css +15 -3
- data/app/assets/tailwind/kiso/dashboard.css +97 -44
- data/app/assets/tailwind/kiso/dialog.css +39 -5
- data/app/assets/tailwind/kiso/engine.css +117 -34
- data/app/assets/tailwind/kiso/input-otp.css +24 -4
- data/app/assets/tailwind/kiso/palettes/blue.css +14 -5
- data/app/assets/tailwind/kiso/palettes/green.css +9 -5
- data/app/assets/tailwind/kiso/palettes/orange.css +9 -5
- data/app/assets/tailwind/kiso/palettes/violet.css +9 -5
- data/app/assets/tailwind/kiso/palettes/zinc.css +11 -7
- data/app/assets/tailwind/kiso/radio-group.css +11 -4
- data/app/assets/tailwind/kiso/slider.css +25 -6
- data/app/assets/tailwind/kiso/tooltip.css +37 -11
- data/app/helpers/kiso/app_component_helper.rb +83 -34
- data/app/helpers/kiso/component_helper.rb +227 -70
- data/app/helpers/kiso/icon_helper.rb +101 -39
- data/app/helpers/kiso/theme_helper.rb +50 -9
- data/app/helpers/kiso/ui_context_helper.rb +87 -35
- data/app/javascript/controllers/kiso/combobox_controller.js +10 -2
- data/app/javascript/controllers/kiso/command_controller.js +2 -0
- data/app/javascript/controllers/kiso/command_dialog_controller.js +4 -0
- data/app/javascript/controllers/kiso/dialog_controller.js +6 -1
- data/app/javascript/controllers/kiso/dialog_trigger_controller.js +1 -1
- data/app/javascript/controllers/kiso/dropdown_menu_controller.js +23 -5
- data/app/javascript/controllers/kiso/index.js +25 -0
- data/app/javascript/controllers/kiso/input_otp_controller.js +5 -3
- data/app/javascript/controllers/kiso/popover_controller.js +18 -4
- data/app/javascript/controllers/kiso/select_controller.js +10 -2
- data/app/javascript/controllers/kiso/sidebar_controller.js +26 -4
- data/app/javascript/controllers/kiso/slider_controller.js +3 -3
- data/app/javascript/controllers/kiso/theme_controller.js +2 -1
- data/app/javascript/controllers/kiso/toggle_controller.js +2 -0
- data/app/javascript/controllers/kiso/toggle_group_controller.js +3 -0
- data/app/javascript/controllers/kiso/tooltip_controller.js +3 -0
- data/app/javascript/kiso/utils/focusable.js +14 -0
- data/app/javascript/kiso/utils/highlight.js +15 -1
- data/app/views/kiso/components/_alert.html.erb +2 -0
- data/app/views/kiso/components/_alert_dialog.html.erb +5 -2
- data/app/views/kiso/components/_app.html.erb +2 -0
- data/app/views/kiso/components/_aspect_ratio.html.erb +1 -0
- data/app/views/kiso/components/_avatar.html.erb +6 -2
- data/app/views/kiso/components/_button.html.erb +3 -0
- data/app/views/kiso/components/_checkbox.html.erb +1 -0
- data/app/views/kiso/components/_color_mode_button.html.erb +2 -0
- data/app/views/kiso/components/_color_mode_select.html.erb +2 -0
- data/app/views/kiso/components/_combobox.html.erb +3 -0
- data/app/views/kiso/components/_command.html.erb +2 -0
- data/app/views/kiso/components/_dashboard_group.html.erb +4 -0
- data/app/views/kiso/components/_dashboard_navbar.html.erb +2 -0
- data/app/views/kiso/components/_dashboard_panel.html.erb +1 -0
- data/app/views/kiso/components/_dashboard_sidebar.html.erb +2 -0
- data/app/views/kiso/components/_dashboard_toolbar.html.erb +2 -0
- data/app/views/kiso/components/_dialog.html.erb +3 -0
- data/app/views/kiso/components/_dropdown_menu.html.erb +2 -0
- data/app/views/kiso/components/_empty.html.erb +2 -0
- data/app/views/kiso/components/_field.html.erb +2 -0
- data/app/views/kiso/components/_field_group.html.erb +1 -0
- data/app/views/kiso/components/_field_set.html.erb +1 -0
- data/app/views/kiso/components/_input_group.html.erb +1 -0
- data/app/views/kiso/components/_input_otp.html.erb +3 -0
- data/app/views/kiso/components/_nav.html.erb +2 -0
- data/app/views/kiso/components/_page_card.html.erb +3 -0
- data/app/views/kiso/components/_page_header.html.erb +3 -0
- data/app/views/kiso/components/_page_section.html.erb +2 -0
- data/app/views/kiso/components/_pagination.html.erb +2 -0
- data/app/views/kiso/components/_popover.html.erb +3 -0
- data/app/views/kiso/components/_select.html.erb +3 -0
- data/app/views/kiso/components/_select_native.html.erb +2 -0
- data/app/views/kiso/components/_separator.html.erb +2 -0
- data/app/views/kiso/components/_skeleton.html.erb +1 -0
- data/app/views/kiso/components/_slider.html.erb +4 -0
- data/app/views/kiso/components/_spinner.html.erb +2 -0
- data/app/views/kiso/components/_stats_card.html.erb +2 -0
- data/app/views/kiso/components/_stats_grid.html.erb +1 -0
- data/app/views/kiso/components/_switch.html.erb +2 -0
- data/app/views/kiso/components/_table.html.erb +2 -0
- data/app/views/kiso/components/_textarea.html.erb +3 -0
- data/app/views/kiso/components/_toggle.html.erb +2 -0
- data/app/views/kiso/components/_toggle_group.html.erb +2 -0
- data/app/views/kiso/components/_tooltip.html.erb +3 -0
- data/app/views/kiso/components/alert_dialog/_action.html.erb +1 -0
- data/app/views/kiso/components/alert_dialog/_cancel.html.erb +1 -0
- data/app/views/kiso/components/alert_dialog/_description.html.erb +1 -0
- data/app/views/kiso/components/alert_dialog/_title.html.erb +1 -0
- data/app/views/kiso/components/avatar/_image.html.erb +1 -0
- data/app/views/kiso/components/breadcrumb/_separator.html.erb +3 -0
- data/app/views/kiso/components/combobox/_chips.html.erb +3 -0
- data/app/views/kiso/components/command/_dialog.html.erb +2 -0
- data/app/views/kiso/components/dashboard_sidebar/_collapse.html.erb +2 -0
- data/app/views/kiso/components/dialog/_close.html.erb +1 -0
- data/app/views/kiso/components/field/_error.html.erb +4 -0
- data/app/views/kiso/components/field/_label.html.erb +2 -0
- data/app/views/kiso/components/field/_separator.html.erb +3 -0
- data/app/views/kiso/components/input_otp/_separator.html.erb +2 -0
- data/app/views/kiso/components/input_otp/_slot.html.erb +2 -0
- data/app/views/kiso/components/nav/_section.html.erb +4 -0
- data/app/views/kiso/components/tooltip/_content.html.erb +2 -0
- data/lib/generators/kiso/install/USAGE +23 -0
- data/lib/generators/kiso/install/install_generator.rb +91 -0
- data/lib/generators/kiso/install/templates/design_system.md.tt +190 -0
- data/lib/generators/kiso/install/templates/initializer.rb.tt +40 -0
- data/lib/kiso/cli/make.rb +6 -3
- data/lib/kiso/cli.rb +10 -0
- data/lib/kiso/color_utils.rb +31 -8
- data/lib/kiso/configuration.rb +11 -0
- data/lib/kiso/engine.rb +9 -2
- data/lib/kiso/propshaft_tailwind_stub_filter.rb +9 -2
- data/lib/kiso/themes/avatar.rb +40 -6
- data/lib/kiso/themes/badge.rb +5 -1
- data/lib/kiso/themes/color_mode_button.rb +11 -0
- data/lib/kiso/themes/color_mode_select.rb +7 -0
- data/lib/kiso/themes/dashboard.rb +28 -0
- data/lib/kiso/themes/dropdown_menu.rb +2 -2
- data/lib/kiso/themes/input_otp.rb +6 -3
- data/lib/kiso/themes/nav.rb +17 -0
- data/lib/kiso/themes/pagination.rb +9 -4
- data/lib/kiso/themes/shared.rb +27 -7
- data/lib/kiso/version.rb +5 -2
- 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
|
-
#
|
|
9
|
-
#
|
|
10
|
-
#
|
|
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
|
-
#
|
|
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
|
-
#
|
|
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
|
-
#
|
|
27
|
-
#
|
|
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
|
-
# @
|
|
30
|
-
# (
|
|
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(:
|
|
37
|
-
#
|
|
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
|
-
#
|
|
49
|
-
#
|
|
50
|
-
#
|
|
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
|
|
53
|
-
# (e.g. +"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
|
|
56
|
-
#
|
|
57
|
-
#
|
|
58
|
-
# @
|
|
59
|
-
#
|
|
60
|
-
# @
|
|
61
|
-
#
|
|
62
|
-
# @example
|
|
63
|
-
# kiso_icon("check"
|
|
64
|
-
#
|
|
65
|
-
#
|
|
66
|
-
#
|
|
67
|
-
#
|
|
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
|
|
132
|
+
# Merges icon class parts via +TailwindMerge+, filtering out blanks.
|
|
79
133
|
#
|
|
80
|
-
#
|
|
81
|
-
#
|
|
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
|
-
#
|
|
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
|
-
#
|
|
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
|
|
44
|
+
# Outputs a blocking inline +<script>+ that prevents dark mode FOUC.
|
|
20
45
|
#
|
|
21
|
-
#
|
|
22
|
-
#
|
|
23
|
-
#
|
|
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
|
-
#
|
|
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
|
-
# @
|
|
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
|
-
# @
|
|
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
|
|
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
|
|
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
|
-
#
|
|
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
|
-
#
|
|
17
|
-
#
|
|
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
|
-
#
|
|
20
|
-
#
|
|
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) #=>
|
|
46
|
+
# # Sub-part reads its slot override:
|
|
47
|
+
# kiso_current_ui(:card)[:header] #=> "p-8"
|
|
25
48
|
#
|
|
26
|
-
# # After parent
|
|
49
|
+
# # After parent finishes rendering (in ensure block):
|
|
27
50
|
# kiso_pop_ui_context(:card)
|
|
28
51
|
#
|
|
29
|
-
# @example scope:
|
|
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
|
|
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
|
-
#
|
|
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
|
-
#
|
|
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
|
-
#
|
|
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
|
|
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
|
-
#
|
|
103
|
+
# Pushes a +scope:+ hash onto the context stack for a component.
|
|
68
104
|
#
|
|
69
|
-
# Scope shares domain locals from a parent component
|
|
70
|
-
# so callers don't have to repeat the same locals on every
|
|
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
|
-
#
|
|
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
|
-
#
|
|
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
|
-
#
|
|
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
|
-
#
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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
|
-
*
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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 {
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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 {
|
|
31
|
-
* @property {
|
|
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
|
*/
|