poetry-core 0.0.2
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 +7 -0
- data/CHANGELOG.md +3 -0
- data/DESIGN.md +145 -0
- data/LICENSE.txt +21 -0
- data/README.md +30 -0
- data/THIRD_PARTY_NOTICES.md +507 -0
- data/app/components/poetry/core/box/component.rb +82 -0
- data/app/components/poetry/core/component.rb +390 -0
- data/app/components/poetry/core/concerns/agent_tools.rb +418 -0
- data/app/components/poetry/core/concerns/declared_attributes.rb +267 -0
- data/app/components/poetry/core/concerns/introspection.rb +533 -0
- data/app/components/poetry/core/concerns/options.rb +262 -0
- data/app/components/poetry/core/concerns/parts.rb +148 -0
- data/app/components/poetry/core/concerns/stimulus.rb +363 -0
- data/app/components/poetry/core/concerns/styles.rb +323 -0
- data/app/components/poetry/core/style.rb +150 -0
- data/app/components/poetry/core/wrapper/component.rb +90 -0
- data/app/javascript/poetry/core/accordion_controller.js +165 -0
- data/app/javascript/poetry/core/action_bar_controller.js +109 -0
- data/app/javascript/poetry/core/autocomplete_controller.js +220 -0
- data/app/javascript/poetry/core/calendar_controller.js +406 -0
- data/app/javascript/poetry/core/carousel_controller.js +167 -0
- data/app/javascript/poetry/core/checkbox_group_controller.js +97 -0
- data/app/javascript/poetry/core/checked_controller.js +208 -0
- data/app/javascript/poetry/core/clipboard_text_controller.js +111 -0
- data/app/javascript/poetry/core/combobox_controller.js +1095 -0
- data/app/javascript/poetry/core/command_controller.js +510 -0
- data/app/javascript/poetry/core/context_menu_controller.js +204 -0
- data/app/javascript/poetry/core/date_field_controller.js +584 -0
- data/app/javascript/poetry/core/date_picker_controller.js +111 -0
- data/app/javascript/poetry/core/deferred_controller.js +111 -0
- data/app/javascript/poetry/core/dialog_controller.js +206 -0
- data/app/javascript/poetry/core/dismissable_controller.js +182 -0
- data/app/javascript/poetry/core/drawer_controller.js +365 -0
- data/app/javascript/poetry/core/file_input_controller.js +191 -0
- data/app/javascript/poetry/core/focus_scope_controller.js +162 -0
- data/app/javascript/poetry/core/helpers/announce.js +228 -0
- data/app/javascript/poetry/core/helpers/breakpoint.js +28 -0
- data/app/javascript/poetry/core/helpers/collection.js +18 -0
- data/app/javascript/poetry/core/helpers/direction.js +15 -0
- data/app/javascript/poetry/core/helpers/escape.js +38 -0
- data/app/javascript/poetry/core/helpers/filter_rank.js +104 -0
- data/app/javascript/poetry/core/helpers/focus_guards.js +46 -0
- data/app/javascript/poetry/core/helpers/hotkey.js +43 -0
- data/app/javascript/poetry/core/helpers/id_integrity.js +65 -0
- data/app/javascript/poetry/core/helpers/incomplete_date.js +328 -0
- data/app/javascript/poetry/core/helpers/mask.js +283 -0
- data/app/javascript/poetry/core/helpers/portal.js +217 -0
- data/app/javascript/poetry/core/helpers/presence.js +210 -0
- data/app/javascript/poetry/core/helpers/registration_guard.js +76 -0
- data/app/javascript/poetry/core/helpers/scroll_lock.js +67 -0
- data/app/javascript/poetry/core/helpers/scroller_geometry.js +422 -0
- data/app/javascript/poetry/core/helpers/state.js +80 -0
- data/app/javascript/poetry/core/helpers/tabbable.js +56 -0
- data/app/javascript/poetry/core/helpers/turbo_cache.js +21 -0
- data/app/javascript/poetry/core/helpers/typeahead.js +85 -0
- data/app/javascript/poetry/core/hotkey_controller.js +55 -0
- data/app/javascript/poetry/core/hover_card_controller.js +431 -0
- data/app/javascript/poetry/core/index.js +218 -0
- data/app/javascript/poetry/core/mask_controller.js +500 -0
- data/app/javascript/poetry/core/menu_controller.js +1050 -0
- data/app/javascript/poetry/core/menubar_controller.js +329 -0
- data/app/javascript/poetry/core/message_scroller_controller.js +930 -0
- data/app/javascript/poetry/core/navigation_menu_controller.js +458 -0
- data/app/javascript/poetry/core/number_field_controller.js +404 -0
- data/app/javascript/poetry/core/optimistic_form_controller.js +56 -0
- data/app/javascript/poetry/core/otp_controller.js +219 -0
- data/app/javascript/poetry/core/popover_controller.js +300 -0
- data/app/javascript/poetry/core/popper_controller.js +439 -0
- data/app/javascript/poetry/core/pressed_controller.js +74 -0
- data/app/javascript/poetry/core/questionnaire_controller.js +468 -0
- data/app/javascript/poetry/core/radio_group_controller.js +197 -0
- data/app/javascript/poetry/core/resizable_controller.js +202 -0
- data/app/javascript/poetry/core/roving_focus_controller.js +215 -0
- data/app/javascript/poetry/core/scroll_spy_controller.js +84 -0
- data/app/javascript/poetry/core/search_field_controller.js +95 -0
- data/app/javascript/poetry/core/select_controller.js +924 -0
- data/app/javascript/poetry/core/sensitive_input_controller.js +195 -0
- data/app/javascript/poetry/core/sheet_controller.js +24 -0
- data/app/javascript/poetry/core/sidebar_controller.js +272 -0
- data/app/javascript/poetry/core/slider_controller.js +419 -0
- data/app/javascript/poetry/core/state_controller.js +82 -0
- data/app/javascript/poetry/core/table_selection_controller.js +153 -0
- data/app/javascript/poetry/core/tabs_controller.js +151 -0
- data/app/javascript/poetry/core/tag_group_controller.js +134 -0
- data/app/javascript/poetry/core/toast_controller.js +228 -0
- data/app/javascript/poetry/core/toast_trigger_controller.js +26 -0
- data/app/javascript/poetry/core/toaster_controller.js +197 -0
- data/app/javascript/poetry/core/toggle_group_controller.js +206 -0
- data/app/javascript/poetry/core/tooltip_controller.js +541 -0
- data/app/javascript/poetry/core/tree_controller.js +265 -0
- data/app/javascript/poetry/core/vendor/floating_ui_core.js +1043 -0
- data/app/javascript/poetry/core/vendor/floating_ui_dom.js +794 -0
- data/app/javascript/poetry/core/vendor/floating_ui_utils.js +141 -0
- data/app/javascript/poetry/core/vendor/floating_ui_utils_dom.js +170 -0
- data/app/views/poetry/core/preview.html.erb +13 -0
- data/config/component_registry.yml +20 -0
- data/config/controllers_manifest.json +1528 -0
- data/config/importmap.rb +11 -0
- data/config/state_vocabulary.json +31 -0
- data/lib/active_model/type/list.rb +33 -0
- data/lib/active_model/type/symbol.rb +36 -0
- data/lib/poetry/core/check/stable_identity.rb +104 -0
- data/lib/poetry/core/check.rb +1445 -0
- data/lib/poetry/core/config.rb +286 -0
- data/lib/poetry/core/contrib/wrapped_helper.rb +68 -0
- data/lib/poetry/core/css/bem_merger.rb +45 -0
- data/lib/poetry/core/css/bem_reference.rb +87 -0
- data/lib/poetry/core/css/override_scan.rb +110 -0
- data/lib/poetry/core/css/resolver.rb +161 -0
- data/lib/poetry/core/css/safelist.rb +41 -0
- data/lib/poetry/core/css/tailwind_merger.rb +102 -0
- data/lib/poetry/core/css/template_classes.rb +102 -0
- data/lib/poetry/core/css/theme_coverage.rb +72 -0
- data/lib/poetry/core/css/var_coverage.rb +75 -0
- data/lib/poetry/core/css/verifier.rb +106 -0
- data/lib/poetry/core/design_lint.rb +720 -0
- data/lib/poetry/core/design_md/import.rb +250 -0
- data/lib/poetry/core/design_md.rb +461 -0
- data/lib/poetry/core/engine.rb +71 -0
- data/lib/poetry/core/errors.rb +53 -0
- data/lib/poetry/core/html/attributes.rb +667 -0
- data/lib/poetry/core/icons.rb +157 -0
- data/lib/poetry/core/llms_text.rb +354 -0
- data/lib/poetry/core/page_architectures.rb +304 -0
- data/lib/poetry/core/part_contract.rb +285 -0
- data/lib/poetry/core/preview/abstract.rb +121 -0
- data/lib/poetry/core/preview/base.rb +227 -0
- data/lib/poetry/core/preview/sidecarable.rb +136 -0
- data/lib/poetry/core/preview/template.rb +186 -0
- data/lib/poetry/core/recipe_items.rb +82 -0
- data/lib/poetry/core/registry.rb +363 -0
- data/lib/poetry/core/registry_address.rb +115 -0
- data/lib/poetry/core/registry_client.rb +217 -0
- data/lib/poetry/core/registry_installer.rb +234 -0
- data/lib/poetry/core/registry_items.rb +184 -0
- data/lib/poetry/core/skill_text.rb +288 -0
- data/lib/poetry/core/stable_id.rb +110 -0
- data/lib/poetry/core/stimulus/builder.rb +395 -0
- data/lib/poetry/core/stimulus/declarations.rb +444 -0
- data/lib/poetry/core/stimulus/manifest.rb +68 -0
- data/lib/poetry/core/stimulus/merger.rb +131 -0
- data/lib/poetry/core/stimulus_contract.rb +263 -0
- data/lib/poetry/core/tag_helper.rb +10 -0
- data/lib/poetry/core/template_compile.rb +74 -0
- data/lib/poetry/core/token_import.rb +317 -0
- data/lib/poetry/core/tokens/color.rb +215 -0
- data/lib/poetry/core/tokens/contrast_gate.rb +120 -0
- data/lib/poetry/core/tokens/generator.rb +211 -0
- data/lib/poetry/core/tokens.rb +117 -0
- data/lib/poetry/core/version.rb +9 -0
- data/lib/poetry/core.rb +52 -0
- data/lib/poetry-core.rb +5 -0
- data/tokens/aliases.css +145 -0
- data/tokens/tailwind-theme.css +47 -0
- data/tokens/tokens.css +78 -0
- data/tokens/tokens.dtcg.json +779 -0
- data/vendor/floating-ui/LICENSE +20 -0
- data/vendor/floating-ui/VENDORED_VERSIONS +15 -0
- data/vendor/shadcn-tailwind/LICENSE +21 -0
- data/vendor/shadcn-tailwind/VENDORED_COMMIT +1 -0
- data/vendor/shadcn-tailwind/tailwind.css +629 -0
- data/vendor/tw-animate-css/LICENSE +21 -0
- data/vendor/tw-animate-css/VENDORED_VERSION +1 -0
- data/vendor/tw-animate-css/tw-animate.css +1 -0
- metadata +267 -0
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Poetry
|
|
4
|
+
module Core
|
|
5
|
+
# Manages configuration settings for the Poetry::Core module.
|
|
6
|
+
#
|
|
7
|
+
# This class provides a flexible configuration system using ActiveSupport::OrderedOptions
|
|
8
|
+
# under the hood, allowing access to configuration values using either hash-style or
|
|
9
|
+
# method-style syntax. It supports both a singleton pattern via {.current} for global
|
|
10
|
+
# configuration and the ability to create custom configuration instances.
|
|
11
|
+
#
|
|
12
|
+
# The configuration system is designed to be easily extensible while providing sensible
|
|
13
|
+
# defaults for all Poetry::Core components.
|
|
14
|
+
#
|
|
15
|
+
# @example Accessing global configuration
|
|
16
|
+
# Poetry::Core::Config.current.classname_merger
|
|
17
|
+
# # => #<Poetry::Core::CSS::TailwindMerger:0x00007f8b1c0a3b40>
|
|
18
|
+
#
|
|
19
|
+
# @example Modifying global configuration
|
|
20
|
+
# Poetry::Core::Config.current.icon_library = :my_icons
|
|
21
|
+
#
|
|
22
|
+
# @example Creating a custom configuration instance
|
|
23
|
+
# config = Poetry::Core::Config.new
|
|
24
|
+
# config.classname_merger = MyCustomMerger.new
|
|
25
|
+
#
|
|
26
|
+
# @example Using hash-style access
|
|
27
|
+
# Poetry::Core::Config.current[:classname_merger]
|
|
28
|
+
# Poetry::Core::Config.current[:custom_setting] = "value"
|
|
29
|
+
#
|
|
30
|
+
# @see Poetry::Core::CSS::TailwindMerger
|
|
31
|
+
# @see Poetry::Core::Stimulus::Merger
|
|
32
|
+
class Config
|
|
33
|
+
class << self
|
|
34
|
+
# Creates a new configuration instance with default settings.
|
|
35
|
+
#
|
|
36
|
+
# This is aliased from the standard {#initialize} method to provide a more
|
|
37
|
+
# semantic way to create default configurations.
|
|
38
|
+
#
|
|
39
|
+
# @return [Poetry::Core::Config] A new configuration instance with default values
|
|
40
|
+
# @example
|
|
41
|
+
# config = Poetry::Core::Config.default
|
|
42
|
+
alias default new
|
|
43
|
+
|
|
44
|
+
# Returns the default configuration values.
|
|
45
|
+
#
|
|
46
|
+
# These defaults are used when initializing new configuration instances and
|
|
47
|
+
# define the standard behavior for all Poetry::Core components.
|
|
48
|
+
#
|
|
49
|
+
# The keys and their defaults:
|
|
50
|
+
#
|
|
51
|
+
# - `classname_merger` ({Poetry::Core::CSS::TailwindMerger}) - resolves
|
|
52
|
+
# conflicting utility classes when caller classes meet component
|
|
53
|
+
# classes.
|
|
54
|
+
# - `stimulus_merger` ({Poetry::Core::Stimulus::Merger}) - combines
|
|
55
|
+
# Stimulus data attributes without duplicating controllers or
|
|
56
|
+
# actions.
|
|
57
|
+
# - `css_mode` (`:tailwind`) - `:tailwind` emits resolved utility
|
|
58
|
+
# classes; `:bem` emits the BEM token IR for bring-your-own-CSS
|
|
59
|
+
# hosts.
|
|
60
|
+
# - `icon_library` (`:lucide`) - the active icon set, by the key it
|
|
61
|
+
# registered under ({Poetry::Core::Icons.register}).
|
|
62
|
+
# - `raise_on_missing_icon` (`nil`) - the policy for a dynamic icon
|
|
63
|
+
# name that resolves to nothing: nil raises in local environments
|
|
64
|
+
# and degrades to the fallback elsewhere; true/false force one
|
|
65
|
+
# behavior.
|
|
66
|
+
# - `icon_fallback` (`:"circle-question-mark"`) - rendered instead
|
|
67
|
+
# of a missing icon when not raising; nil re-raises.
|
|
68
|
+
# - `on_missing_icon` (`nil`) - an optional callable
|
|
69
|
+
# `(name:, library:, error:)` fired before the fallback renders.
|
|
70
|
+
# - `webmcp_registration_budget` (`20`) - the per-document cap on
|
|
71
|
+
# WebMCP tool registrations a page's opted-in instances may make;
|
|
72
|
+
# poetry-agent's `registration_budget` setting writes through.
|
|
73
|
+
# - `stable_id_mode` (`:off`) - the opt-in `:sequence` mode seeds a
|
|
74
|
+
# per-request deterministic id sequence (read the hazards in
|
|
75
|
+
# StableId before enabling).
|
|
76
|
+
# - `stable_id_seed` - the request-to-seed callable for that mode
|
|
77
|
+
# (defaults to the request path).
|
|
78
|
+
#
|
|
79
|
+
# @return [ActiveSupport::OrderedOptions] the default configuration
|
|
80
|
+
#
|
|
81
|
+
# @example Getting default values
|
|
82
|
+
# defaults = Poetry::Core::Config.defaults
|
|
83
|
+
# defaults.classname_merger # => #<Poetry::Core::CSS::TailwindMerger:0x00007f8b1c0a3b40>
|
|
84
|
+
# defaults.css_mode # => :tailwind
|
|
85
|
+
def defaults
|
|
86
|
+
ActiveSupport::OrderedOptions.new.merge!({
|
|
87
|
+
classname_merger: Poetry::Core::CSS::TailwindMerger.new,
|
|
88
|
+
stimulus_merger: Poetry::Core::Stimulus::Merger.new,
|
|
89
|
+
# :tailwind emits resolved utility classes (default);
|
|
90
|
+
# :bem emits the BEM token IR for bring-your-own-CSS
|
|
91
|
+
# hosts (no :both, deliberately).
|
|
92
|
+
css_mode: :tailwind,
|
|
93
|
+
# The active icon set (Lucide default; sets
|
|
94
|
+
# register via Poetry::Core::Icons.register).
|
|
95
|
+
icon_library: :lucide,
|
|
96
|
+
# The missing-icon policy. Static literals
|
|
97
|
+
# are caught by poetry check; a DYNAMIC name (a DB
|
|
98
|
+
# value, a user setting) surfaces at render. nil =
|
|
99
|
+
# auto: raise in Rails.env.local?, degrade to
|
|
100
|
+
# icon_fallback elsewhere. true/false force it.
|
|
101
|
+
raise_on_missing_icon: nil,
|
|
102
|
+
# StableId sequence mode (OPT-IN, experimental):
|
|
103
|
+
# :off (default) or :sequence - the engine's
|
|
104
|
+
# around_action seeds a per-request deterministic
|
|
105
|
+
# id sequence. Read the hazards in StableId before
|
|
106
|
+
# enabling; keyed identity (key:) is the general
|
|
107
|
+
# answer, this mode is for byte-stable content
|
|
108
|
+
# pages only.
|
|
109
|
+
stable_id_mode: :off,
|
|
110
|
+
stable_id_seed: ->(request) { request.path }, # rubocop:disable Style/SymbolProc -- the documented override shape
|
|
111
|
+
# Rendered instead of a missing icon when not
|
|
112
|
+
# raising; nil re-raises. Must exist in every
|
|
113
|
+
# registered set (Lucide ships it).
|
|
114
|
+
icon_fallback: :"circle-question-mark",
|
|
115
|
+
# Optional callable(name:, library:, error:) fired
|
|
116
|
+
# before the fallback renders - the instrumentation
|
|
117
|
+
# seam (log, notify, count). Fires per render.
|
|
118
|
+
on_missing_icon: nil,
|
|
119
|
+
# The per-document WebMCP registration budget
|
|
120
|
+
# (Concerns::AgentTools::WEBMCP_DEFAULT_BUDGET):
|
|
121
|
+
# the registrar drops registrations past it with
|
|
122
|
+
# a console warning. Rides the opted-in root only
|
|
123
|
+
# when it differs from the runtime's default.
|
|
124
|
+
webmcp_registration_budget: 20
|
|
125
|
+
})
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# Returns the global singleton configuration instance.
|
|
129
|
+
#
|
|
130
|
+
# This method provides access to the shared configuration used throughout the
|
|
131
|
+
# application. The instance is created lazily on first access and persists for
|
|
132
|
+
# the lifetime of the application.
|
|
133
|
+
#
|
|
134
|
+
# @return [Poetry::Core::Config] The global configuration instance
|
|
135
|
+
#
|
|
136
|
+
# @example Accessing global settings
|
|
137
|
+
# Poetry::Core::Config.current.css_mode
|
|
138
|
+
# # => :tailwind
|
|
139
|
+
#
|
|
140
|
+
# @example Modifying global settings
|
|
141
|
+
# Poetry::Core::Config.current.classname_merger = CustomMerger.new
|
|
142
|
+
#
|
|
143
|
+
# @note Changes to the global configuration will affect all Poetry::Core components
|
|
144
|
+
# throughout the application.
|
|
145
|
+
def current
|
|
146
|
+
@current ||= default
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
# @!method classname_merger
|
|
151
|
+
# The merger that resolves conflicting utility classes when caller
|
|
152
|
+
# classes meet component classes.
|
|
153
|
+
# @return [Poetry::Core::CSS::TailwindMerger] The CSS class merger instance
|
|
154
|
+
#
|
|
155
|
+
# @!method classname_merger=(merger)
|
|
156
|
+
# Replaces the class merger.
|
|
157
|
+
# @param merger [Poetry::Core::CSS::TailwindMerger] The CSS class merger instance to use
|
|
158
|
+
# @return [Poetry::Core::CSS::TailwindMerger]
|
|
159
|
+
#
|
|
160
|
+
# @!method stimulus_merger
|
|
161
|
+
# The merger that combines Stimulus data attributes without
|
|
162
|
+
# duplicating controllers or actions.
|
|
163
|
+
# @return [Poetry::Core::Stimulus::Merger] The Stimulus attribute merger instance
|
|
164
|
+
#
|
|
165
|
+
# @!method stimulus_merger=(merger)
|
|
166
|
+
# Replaces the Stimulus attribute merger.
|
|
167
|
+
# @param merger [Poetry::Core::Stimulus::Merger] The Stimulus attribute merger instance to use
|
|
168
|
+
# @return [Poetry::Core::Stimulus::Merger]
|
|
169
|
+
#
|
|
170
|
+
# @!method css_mode
|
|
171
|
+
# The class emission mode: `:tailwind` resolves style values to
|
|
172
|
+
# utility classes, `:bem` emits the BEM token IR.
|
|
173
|
+
# @return [Symbol] :tailwind or :bem
|
|
174
|
+
#
|
|
175
|
+
# @!method css_mode=(mode)
|
|
176
|
+
# Sets the class emission mode.
|
|
177
|
+
# @param mode [Symbol] :tailwind or :bem
|
|
178
|
+
# @return [Symbol]
|
|
179
|
+
#
|
|
180
|
+
# @!method icon_library
|
|
181
|
+
# The key of the active icon set ({Poetry::Core::Icons.register}).
|
|
182
|
+
# @return [Symbol]
|
|
183
|
+
#
|
|
184
|
+
# @!method icon_library=(key)
|
|
185
|
+
# Selects the active icon set.
|
|
186
|
+
# @param key [Symbol, String] a registered library key
|
|
187
|
+
# @return [Symbol, String]
|
|
188
|
+
#
|
|
189
|
+
# @!method raise_on_missing_icon
|
|
190
|
+
# The policy for a dynamic icon name that resolves to nothing: nil
|
|
191
|
+
# raises in local environments and degrades to the fallback
|
|
192
|
+
# elsewhere; true/false force one behavior.
|
|
193
|
+
# @return [Boolean, nil]
|
|
194
|
+
#
|
|
195
|
+
# @!method raise_on_missing_icon=(policy)
|
|
196
|
+
# Sets the missing-icon policy.
|
|
197
|
+
# @param policy [Boolean, nil]
|
|
198
|
+
# @return [Boolean, nil]
|
|
199
|
+
#
|
|
200
|
+
# @!method icon_fallback
|
|
201
|
+
# The icon rendered instead of a missing one when not raising; nil
|
|
202
|
+
# re-raises. Must exist in every registered set.
|
|
203
|
+
# @return [Symbol, nil]
|
|
204
|
+
#
|
|
205
|
+
# @!method icon_fallback=(name)
|
|
206
|
+
# Sets the fallback icon.
|
|
207
|
+
# @param name [Symbol, nil]
|
|
208
|
+
# @return [Symbol, nil]
|
|
209
|
+
#
|
|
210
|
+
# @!method webmcp_registration_budget
|
|
211
|
+
# The per-document WebMCP registration budget the registrar
|
|
212
|
+
# enforces (default 20).
|
|
213
|
+
# @return [Integer]
|
|
214
|
+
#
|
|
215
|
+
# @!method webmcp_registration_budget=(count)
|
|
216
|
+
# Sets the per-document WebMCP registration budget.
|
|
217
|
+
# @param count [Integer]
|
|
218
|
+
# @return [Integer]
|
|
219
|
+
#
|
|
220
|
+
# @!method on_missing_icon
|
|
221
|
+
# The instrumentation hook fired before a fallback icon renders,
|
|
222
|
+
# called with `name:`, `library:`, and `error:`.
|
|
223
|
+
# @return [#call, nil]
|
|
224
|
+
#
|
|
225
|
+
# @!method on_missing_icon=(callable)
|
|
226
|
+
# Sets the missing-icon hook.
|
|
227
|
+
# @param callable [#call, nil]
|
|
228
|
+
# @return [#call, nil]
|
|
229
|
+
#
|
|
230
|
+
# @!method stable_id_mode
|
|
231
|
+
# The StableId sequence mode: `:off`, or the opt-in `:sequence`.
|
|
232
|
+
# @return [Symbol]
|
|
233
|
+
#
|
|
234
|
+
# @!method stable_id_mode=(mode)
|
|
235
|
+
# Sets the StableId sequence mode.
|
|
236
|
+
# @param mode [Symbol] :off or :sequence
|
|
237
|
+
# @return [Symbol]
|
|
238
|
+
#
|
|
239
|
+
# @!method stable_id_seed
|
|
240
|
+
# The request-to-seed callable the `:sequence` mode derives its
|
|
241
|
+
# per-request id sequence from.
|
|
242
|
+
# @return [#call]
|
|
243
|
+
#
|
|
244
|
+
# @!method stable_id_seed=(callable)
|
|
245
|
+
# Sets the seed callable.
|
|
246
|
+
# @param callable [#call] receives the request, returns the seed
|
|
247
|
+
# @return [#call]
|
|
248
|
+
|
|
249
|
+
# The declared configuration surface: every key {.defaults} ships.
|
|
250
|
+
# Each is a real reader/writer pair (delegated just below); unknown
|
|
251
|
+
# keys still flow through delegate_missing_to, so hosts may stash
|
|
252
|
+
# their own values.
|
|
253
|
+
SETTINGS = %i[classname_merger stimulus_merger css_mode icon_library
|
|
254
|
+
raise_on_missing_icon icon_fallback on_missing_icon
|
|
255
|
+
stable_id_mode stable_id_seed
|
|
256
|
+
webmcp_registration_budget].freeze
|
|
257
|
+
|
|
258
|
+
delegate(*SETTINGS, *SETTINGS.map { |key| :"#{key}=" }, to: :@config)
|
|
259
|
+
|
|
260
|
+
# Delegates all method calls to the internal configuration object.
|
|
261
|
+
#
|
|
262
|
+
# This allows the Config instance to act as a transparent wrapper around
|
|
263
|
+
# ActiveSupport::OrderedOptions, supporting both method-style and hash-style
|
|
264
|
+
# access to configuration values.
|
|
265
|
+
#
|
|
266
|
+
# @api private
|
|
267
|
+
delegate_missing_to :@config
|
|
268
|
+
|
|
269
|
+
# Initializes a new configuration instance with default values.
|
|
270
|
+
#
|
|
271
|
+
# The new instance gets a clone of the default configuration, ensuring each
|
|
272
|
+
# configuration object is independent and modifications won't affect the defaults
|
|
273
|
+
# or other instances.
|
|
274
|
+
#
|
|
275
|
+
# @return [Poetry::Core::Config] A new configuration instance
|
|
276
|
+
#
|
|
277
|
+
# @example Creating an isolated configuration
|
|
278
|
+
# config = Poetry::Core::Config.new
|
|
279
|
+
# config.css_mode = :bem
|
|
280
|
+
# Poetry::Core::Config.current.css_mode # => :tailwind (unchanged)
|
|
281
|
+
def initialize
|
|
282
|
+
@config = self.class.defaults.clone
|
|
283
|
+
end
|
|
284
|
+
end
|
|
285
|
+
end
|
|
286
|
+
end
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Poetry
|
|
4
|
+
module Core
|
|
5
|
+
# Opt-in helper mixins shipped alongside the core.
|
|
6
|
+
module Contrib
|
|
7
|
+
# Provides a convenient method to wrap components with custom HTML code.
|
|
8
|
+
# Adapted from an MIT-licensed source (source and license in
|
|
9
|
+
# THIRD_PARTY_NOTICES.md).
|
|
10
|
+
#
|
|
11
|
+
# This module adds the `#wrapped` method to components, allowing them to be
|
|
12
|
+
# easily wrapped with a {Poetry::Core::Wrapper::Component}. The wrapper component
|
|
13
|
+
# enables adding custom HTML around a component without modifying the
|
|
14
|
+
# component itself, and respects the component's `render?` conditional logic.
|
|
15
|
+
#
|
|
16
|
+
# @example Basic usage
|
|
17
|
+
# class MyComponent < Poetry::Core::Component
|
|
18
|
+
# # WrappedHelper is already included via Poetry::Core::Component
|
|
19
|
+
# end
|
|
20
|
+
#
|
|
21
|
+
# # In a view or template
|
|
22
|
+
# component = MyComponent.new(title: "Hello")
|
|
23
|
+
# wrapper = component.wrapped
|
|
24
|
+
#
|
|
25
|
+
# @example Using in a view template with custom wrapper HTML
|
|
26
|
+
# <%# app/components/my_component/component.html.erb %>
|
|
27
|
+
# <% component = MyComponent.new(title: "Hello") %>
|
|
28
|
+
# <%= render component.wrapped do |wrapper| %>
|
|
29
|
+
# <div class="custom-wrapper">
|
|
30
|
+
# <h2>Wrapped Content:</h2>
|
|
31
|
+
# <%= wrapper.component %>
|
|
32
|
+
# </div>
|
|
33
|
+
# <% end %>
|
|
34
|
+
#
|
|
35
|
+
# @example Conditional rendering
|
|
36
|
+
# # The wrapper only renders if the wrapped component's render? returns true
|
|
37
|
+
# class ConditionalComponent < Poetry::Core::Component
|
|
38
|
+
# def render?
|
|
39
|
+
# @show_content
|
|
40
|
+
# end
|
|
41
|
+
# end
|
|
42
|
+
#
|
|
43
|
+
# # If @show_content is false, neither the wrapper nor component will render
|
|
44
|
+
# <%= render component.wrapped do |wrapper| %>
|
|
45
|
+
# <div class="wrapper"><%= wrapper.component %></div>
|
|
46
|
+
# <% end %>
|
|
47
|
+
#
|
|
48
|
+
# @see Poetry::Core::Wrapper::Component
|
|
49
|
+
module WrappedHelper
|
|
50
|
+
# Wraps the current component instance in a {Poetry::Core::Wrapper::Component}.
|
|
51
|
+
#
|
|
52
|
+
# This creates a wrapper that can be rendered with custom HTML surrounding
|
|
53
|
+
# the component. The wrapper respects the wrapped component's `render?`
|
|
54
|
+
# method, only rendering if it returns true.
|
|
55
|
+
#
|
|
56
|
+
# @return [Poetry::Core::Wrapper::Component] a wrapper component containing self
|
|
57
|
+
#
|
|
58
|
+
# @example
|
|
59
|
+
# component = MyComponent.new
|
|
60
|
+
# wrapper = component.wrapped
|
|
61
|
+
# wrapper.component_instance # => the original component
|
|
62
|
+
def wrapped
|
|
63
|
+
Poetry::Core::Wrapper::Component.new(self)
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Poetry
|
|
4
|
+
module Core
|
|
5
|
+
module CSS
|
|
6
|
+
# The BEM-mode classname merger: the classname_merger a host pairs
|
|
7
|
+
# with `css_mode = :bem`. Same contract as {TailwindMerger}
|
|
8
|
+
# (flatten, stringify, drop blanks, nil for empty input) with
|
|
9
|
+
# exactly two behaviors on top: order-preserving token dedupe and
|
|
10
|
+
# a space join - no Tailwind conflict semantics applied to a BEM
|
|
11
|
+
# host's classes.
|
|
12
|
+
#
|
|
13
|
+
# Deliberately NO modifier-axis conflict resolution: the BEM
|
|
14
|
+
# modifier grammar is dictionary-dependent at the string level
|
|
15
|
+
# (values carry dashes - `--align-inline-start` - and names carry
|
|
16
|
+
# underscores), so a string merger guessing axes would be wrong.
|
|
17
|
+
# Style axes are driven through component options; conflicts
|
|
18
|
+
# between raw caller classes belong to the host stylesheet's
|
|
19
|
+
# cascade.
|
|
20
|
+
#
|
|
21
|
+
# @example Pairing with BEM mode (config/initializers/poetry.rb)
|
|
22
|
+
# Poetry::Core::Config.current.css_mode = :bem
|
|
23
|
+
# Poetry::Core::Config.current.classname_merger =
|
|
24
|
+
# Poetry::Core::CSS::BemMerger.new
|
|
25
|
+
#
|
|
26
|
+
# @example Dedupe without Tailwind semantics
|
|
27
|
+
# Poetry::Core::CSS::BemMerger.new.merge("pill pill--variant-danger", "pill", "p-4 p-2")
|
|
28
|
+
# # => "pill pill--variant-danger p-4 p-2"
|
|
29
|
+
class BemMerger
|
|
30
|
+
# Merges class lists BEM-style: normalize, dedupe at the token
|
|
31
|
+
# level (first occurrence keeps its position), join.
|
|
32
|
+
#
|
|
33
|
+
# @param classes [Array<String, Symbol, Array, nil>] class names,
|
|
34
|
+
# arrays of class names, or nils
|
|
35
|
+
# @return [String, nil] the merged classes, or nil when empty
|
|
36
|
+
def merge(*classes)
|
|
37
|
+
normalized = classes.flatten.compact_blank.map(&:to_s)
|
|
38
|
+
return nil if normalized.empty?
|
|
39
|
+
|
|
40
|
+
normalized.flat_map(&:split).uniq.join(" ")
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Poetry
|
|
4
|
+
module Core
|
|
5
|
+
module CSS
|
|
6
|
+
# Generates the reference stylesheet for `css_mode = :bem` from a Style
|
|
7
|
+
# dictionary: every BEM class the component emits, as a documented CSS
|
|
8
|
+
# selector skeleton the consumer fills with their own rules. Each
|
|
9
|
+
# selector carries a `tailwind-equivalent` comment - the utilities the
|
|
10
|
+
# default (Tailwind) theme resolves that token to - so the reference
|
|
11
|
+
# doubles as the class contract's documentation.
|
|
12
|
+
#
|
|
13
|
+
# The header embeds the dictionary's capsule digest as a leak-guard:
|
|
14
|
+
# CSS written against an older dictionary is detectable by digest
|
|
15
|
+
# mismatch instead of silently drifting on upgrade.
|
|
16
|
+
#
|
|
17
|
+
# @example
|
|
18
|
+
# Poetry::Core::CSS::BemReference.new(MyApp::Button::Style).css
|
|
19
|
+
# # => "/* poetry BEM reference for `.button` - capsule 1a2b3c... */\n.button { ... }"
|
|
20
|
+
#
|
|
21
|
+
# @api private
|
|
22
|
+
class BemReference
|
|
23
|
+
def initialize(style_class, block: style_class.bem_block)
|
|
24
|
+
raise ArgumentError, "#{style_class} has no derivable BEM block" unless block
|
|
25
|
+
|
|
26
|
+
@resolver = style_class.resolver
|
|
27
|
+
@block = block
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def css
|
|
31
|
+
[header, root_rule, element_rules, variant_rules, compound_rules]
|
|
32
|
+
.flatten.compact.join("\n")
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
private
|
|
36
|
+
|
|
37
|
+
def header
|
|
38
|
+
<<~CSS
|
|
39
|
+
/* poetry BEM reference for `.#{@block}` - capsule #{@resolver.digest}
|
|
40
|
+
Generated from the Style dictionary; consumers on css_mode = :bem
|
|
41
|
+
style these selectors with their own CSS. The tailwind-equivalent
|
|
42
|
+
comments show what the default (Tailwind) theme resolves each
|
|
43
|
+
token to. */
|
|
44
|
+
CSS
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def root_rule
|
|
48
|
+
rule(".#{@block}", @resolver.bases.join(" "))
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def element_rules
|
|
52
|
+
@resolver.elements.map do |name, classes|
|
|
53
|
+
rule(".#{@block}__#{name}", classes.join(" "))
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def variant_rules
|
|
58
|
+
@resolver.variants.flat_map do |attr, mapping|
|
|
59
|
+
mapping.map { |value, classes| rule(modifier_selector(attr, value), classes) }
|
|
60
|
+
end.compact
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def compound_rules
|
|
64
|
+
@resolver.compounds.map do |compound|
|
|
65
|
+
selector = compound.criteria.map { |attr, value| modifier_selector(attr, value) }.join
|
|
66
|
+
rule(selector, compound.classes)
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Boolean modifiers are presence classes (`block--attr`); `false` has
|
|
71
|
+
# no class and produces no rule.
|
|
72
|
+
def modifier_selector(attr, value)
|
|
73
|
+
return nil if value == false
|
|
74
|
+
|
|
75
|
+
value == true ? ".#{@block}--#{attr}" : ".#{@block}--#{attr}-#{value}"
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def rule(selector, classes)
|
|
79
|
+
return nil if selector.nil?
|
|
80
|
+
|
|
81
|
+
body = classes.to_s.strip.empty? ? "/* (no default styles) */" : "/* tailwind-equivalent: #{classes} */"
|
|
82
|
+
"#{selector} { #{body} }"
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Poetry
|
|
4
|
+
module Core
|
|
5
|
+
module CSS
|
|
6
|
+
# The .cn-* override contract - an intent-vs-accident distinction for
|
|
7
|
+
# poetry's one un-contracted styling surface. Hosts MAY restyle
|
|
8
|
+
# theme-owned cn-* classes from their own CSS (any unlayered rule
|
|
9
|
+
# beats the theme's layer(base)) - but every such override must be
|
|
10
|
+
# DECLARED: a dated, reasoned, scoped entry under `overrides:` in
|
|
11
|
+
# config/poetry_components.yml. Undeclared overrides are drift;
|
|
12
|
+
# declared ones are design intent that travels into DESIGN.md's
|
|
13
|
+
# "Intentional deviations" section.
|
|
14
|
+
#
|
|
15
|
+
# Pure logic: {relative_path => css} sources + raw declaration hashes
|
|
16
|
+
# in, findings out. The poetry:design:overrides task feeds and prints
|
|
17
|
+
# it. Declaration rules: `reason` is required; `cn: "*"` must be
|
|
18
|
+
# file-scoped - a repo-wide blanket cannot happen by accident.
|
|
19
|
+
#
|
|
20
|
+
# @example
|
|
21
|
+
# scan = Poetry::Core::CSS::OverrideScan.new(
|
|
22
|
+
# sources: { "app/assets/site.css" => css },
|
|
23
|
+
# declarations: YAML.load_file("config/poetry_components.yml")["overrides"]
|
|
24
|
+
# )
|
|
25
|
+
# scan.ok? || scan.undeclared # => [["app/assets/site.css", ["cn-button"]]]
|
|
26
|
+
#
|
|
27
|
+
# @api private
|
|
28
|
+
class OverrideScan
|
|
29
|
+
CN_TOKEN = /\.(cn-[a-z0-9-]+)/
|
|
30
|
+
COMMENT = %r{/\*.*?\*/}m
|
|
31
|
+
|
|
32
|
+
Declaration = Struct.new(:cn, :files, :reason, :created, :index, :matched, keyword_init: true) do
|
|
33
|
+
def wildcard? = cn == "*"
|
|
34
|
+
|
|
35
|
+
def covers?(path, cn_class)
|
|
36
|
+
return false unless wildcard? || Array(cn).include?(cn_class)
|
|
37
|
+
return true if files.nil? || files.empty?
|
|
38
|
+
|
|
39
|
+
files.any? { |glob| File.fnmatch(glob, path, File::FNM_PATHNAME | File::FNM_EXTGLOB) }
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
attr_reader :undeclared, :invalid, :stale, :declared_count
|
|
44
|
+
|
|
45
|
+
def initialize(sources:, declarations:)
|
|
46
|
+
@declarations, @invalid = normalize(Array(declarations))
|
|
47
|
+
@undeclared = []
|
|
48
|
+
@declared_count = 0
|
|
49
|
+
scan(sources)
|
|
50
|
+
@stale = @declarations.reject(&:matched)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def ok? = @undeclared.empty? && @invalid.empty?
|
|
54
|
+
|
|
55
|
+
# The exact YAML to paste for an undeclared override - a finding
|
|
56
|
+
# ships its own exception command.
|
|
57
|
+
def snippet_for(path, classes)
|
|
58
|
+
cn = classes.size == 1 ? classes.first.inspect : "\"*\""
|
|
59
|
+
<<~YAML
|
|
60
|
+
- cn: #{cn}
|
|
61
|
+
files: ["#{path}"]
|
|
62
|
+
reason: "TODO - why this override is intentional"
|
|
63
|
+
created: #{Time.now.strftime("%Y-%m-%d")}
|
|
64
|
+
YAML
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
private
|
|
68
|
+
|
|
69
|
+
def normalize(raw)
|
|
70
|
+
valid = []
|
|
71
|
+
invalid = []
|
|
72
|
+
raw.each_with_index do |entry, index|
|
|
73
|
+
unless entry.is_a?(Hash)
|
|
74
|
+
invalid << "overrides[#{index}]: not a mapping"
|
|
75
|
+
next
|
|
76
|
+
end
|
|
77
|
+
files = Array(entry["files"]).map(&:to_s)
|
|
78
|
+
declaration = Declaration.new(cn: entry["cn"] || "*", files: files,
|
|
79
|
+
reason: entry["reason"].to_s, created: entry["created"],
|
|
80
|
+
index: index, matched: false)
|
|
81
|
+
if declaration.reason.strip.empty?
|
|
82
|
+
invalid << "overrides[#{index}]: `reason` is required - an override without a why is drift"
|
|
83
|
+
elsif declaration.wildcard? && files.empty?
|
|
84
|
+
invalid << "overrides[#{index}]: `cn: \"*\"` must be file-scoped (add `files:`) - " \
|
|
85
|
+
"a repo-wide blanket cannot happen by accident"
|
|
86
|
+
else
|
|
87
|
+
valid << declaration
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
[valid, invalid]
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def scan(sources)
|
|
94
|
+
sources.each do |path, css|
|
|
95
|
+
classes = css.gsub(COMMENT, "").scan(CN_TOKEN).flatten.uniq.sort
|
|
96
|
+
next if classes.empty?
|
|
97
|
+
|
|
98
|
+
open = classes.reject do |cn_class|
|
|
99
|
+
hit = @declarations.select { |declaration| declaration.covers?(path, cn_class) }
|
|
100
|
+
hit.each { |declaration| declaration.matched = true }
|
|
101
|
+
@declared_count += 1 if hit.any?
|
|
102
|
+
hit.any?
|
|
103
|
+
end
|
|
104
|
+
@undeclared << [path, open] if open.any?
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|