poetry-core 0.0.2 → 0.0.3
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/.gitattributes +5 -0
- data/app/javascript/poetry/core/date_field_controller.js +27 -13
- data/app/javascript/poetry/core/helpers/incomplete_date.js +29 -0
- data/lib/poetry/core/check.rb +4 -4
- data/lib/poetry/core/css/template_classes.rb +2 -2
- data/lib/poetry/core/design_lint.rb +2 -2
- data/lib/poetry/core/skill_text.rb +30 -1
- data/lib/poetry/core/stimulus/merger.rb +2 -3
- data/lib/poetry/core/version.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f9fa980bdf8c3d65b51511142615257e850e3ce355fda679dec434aa34e0e3d9
|
|
4
|
+
data.tar.gz: fb7a6d93fec947bcaa4934f70163679a0c0c196e54985012e821e6d1840b614f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: aab549c214fc3ce8312692c3cca3cbf2daf0ee551fed9b9514a02110fe0d31542a5f8b8a972682bf0a11e53fcb99ef8f378453284151e6078ce532f83faf23a7
|
|
7
|
+
data.tar.gz: 8137bf971f7ececd840a190c61deba49fc29408fab30d9696b5f8ce7fd3b934db22585f3070fb0effff09532d1c0992b02831d4590b78cb3337e6aa2cb83a563
|
data/.gitattributes
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
# GitHub language stats (Linguist): the vendored floating-ui dists and the
|
|
2
|
+
# JS test suite are not this gem's source; without this the repo reads as
|
|
3
|
+
# JavaScript instead of Ruby.
|
|
4
|
+
app/javascript/poetry/core/vendor/** linguist-vendored
|
|
5
|
+
test/javascript/** linguist-vendored
|
|
@@ -27,7 +27,8 @@ const isApplePlatform = () => /iP(hone|ad|od)/.test(navigator.userAgent) ||
|
|
|
27
27
|
|
|
28
28
|
const EDITABLE = {
|
|
29
29
|
date: ["year", "month", "day"],
|
|
30
|
-
time: ["hour", "minute", "second", "dayPeriod"]
|
|
30
|
+
time: ["hour", "minute", "second", "dayPeriod"],
|
|
31
|
+
datetime: ["year", "month", "day", "hour", "minute", "second", "dayPeriod"]
|
|
31
32
|
}
|
|
32
33
|
|
|
33
34
|
export default class DateFieldController extends Controller {
|
|
@@ -109,8 +110,15 @@ export default class DateFieldController extends Controller {
|
|
|
109
110
|
|
|
110
111
|
// --- build ---
|
|
111
112
|
|
|
113
|
+
// The native input's type is the contract: date, time, or (datetime-local)
|
|
114
|
+
// both runs in one row, joined by the locale's own separator.
|
|
112
115
|
get #kind() {
|
|
113
|
-
|
|
116
|
+
const type = this.inputTarget.type
|
|
117
|
+
|
|
118
|
+
if (type === "time") return "time"
|
|
119
|
+
if (type === "datetime-local") return "datetime"
|
|
120
|
+
|
|
121
|
+
return "date"
|
|
114
122
|
}
|
|
115
123
|
|
|
116
124
|
#resolvedHourCycle() {
|
|
@@ -122,13 +130,15 @@ export default class DateFieldController extends Controller {
|
|
|
122
130
|
}
|
|
123
131
|
|
|
124
132
|
#formatterOptions() {
|
|
125
|
-
|
|
133
|
+
const date = { year: "numeric", month: "numeric", day: "numeric" }
|
|
134
|
+
|
|
135
|
+
if (this.#kind === "date") return date
|
|
126
136
|
|
|
127
137
|
const options = { hour: "numeric", minute: "2-digit", hourCycle: this.#value.hourCycle }
|
|
128
138
|
|
|
129
139
|
if (this.secondsValue) options.second = "2-digit"
|
|
130
140
|
|
|
131
|
-
return options
|
|
141
|
+
return this.#kind === "datetime" ? { ...date, ...options } : options
|
|
132
142
|
}
|
|
133
143
|
|
|
134
144
|
#buildSegments() {
|
|
@@ -145,7 +155,7 @@ export default class DateFieldController extends Controller {
|
|
|
145
155
|
|
|
146
156
|
// RTL correctness: the time run is wrapped in LRI/PDI isolates so
|
|
147
157
|
// minute:hour never renders reversed (harmless in LTR).
|
|
148
|
-
if (this.#kind
|
|
158
|
+
if (this.#kind !== "date") fragment.appendChild(this.#literal(""))
|
|
149
159
|
|
|
150
160
|
for (const part of parts) {
|
|
151
161
|
const type = part.type === "dayperiod" ? "dayPeriod" : part.type
|
|
@@ -166,7 +176,7 @@ export default class DateFieldController extends Controller {
|
|
|
166
176
|
}
|
|
167
177
|
}
|
|
168
178
|
|
|
169
|
-
if (this.#kind
|
|
179
|
+
if (this.#kind !== "date") fragment.appendChild(this.#literal(""))
|
|
170
180
|
|
|
171
181
|
this.groupTarget.replaceChildren(fragment)
|
|
172
182
|
this.#labelSegments()
|
|
@@ -268,7 +278,7 @@ export default class DateFieldController extends Controller {
|
|
|
268
278
|
}
|
|
269
279
|
|
|
270
280
|
#resolveDayPeriodNames() {
|
|
271
|
-
if (this.#kind
|
|
281
|
+
if (this.#kind === "date" || !this.#value.twelveHour) return
|
|
272
282
|
|
|
273
283
|
const formatter = new Intl.DateTimeFormat(this.#locale(), { hour: "numeric", hourCycle: this.#value.hourCycle })
|
|
274
284
|
const nameAt = (hour) =>
|
|
@@ -283,16 +293,18 @@ export default class DateFieldController extends Controller {
|
|
|
283
293
|
const raw = this.inputTarget.value
|
|
284
294
|
|
|
285
295
|
if (this.#kind === "date") this.#value.setFromISODate(raw)
|
|
286
|
-
else this.#value.setFromISOTime(raw)
|
|
296
|
+
else if (this.#kind === "time") this.#value.setFromISOTime(raw)
|
|
297
|
+
else this.#value.setFromISODateTime(raw)
|
|
287
298
|
}
|
|
288
299
|
|
|
289
300
|
#placeholderFor(type) {
|
|
290
301
|
const seed = new IncompleteDate(this.#value.hourCycle)
|
|
291
302
|
const iso = this.placeholderValue
|
|
292
303
|
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
304
|
+
const [datePart, timePart] = this.#kind === "datetime" ? String(iso ?? "").split("T") : [iso, iso]
|
|
305
|
+
|
|
306
|
+
if (this.#kind !== "time" && !seed.setFromISODate(datePart)) { seed.year = 2020; seed.month = 1; seed.day = 1 }
|
|
307
|
+
if (this.#kind !== "date" && !seed.setFromISOTime(timePart)) {
|
|
296
308
|
seed.setFromH23(12)
|
|
297
309
|
seed.minute = 0
|
|
298
310
|
seed.second = 0
|
|
@@ -542,7 +554,7 @@ export default class DateFieldController extends Controller {
|
|
|
542
554
|
}
|
|
543
555
|
|
|
544
556
|
#commitIfComplete() {
|
|
545
|
-
if (this.#kind
|
|
557
|
+
if (this.#kind !== "time" && this.#value.isComplete(["year", "month", "day"]) &&
|
|
546
558
|
!this.#value.isValidDate()) return // Feb 31 mid-edit: hold the wire
|
|
547
559
|
|
|
548
560
|
this.#syncInput()
|
|
@@ -551,7 +563,9 @@ export default class DateFieldController extends Controller {
|
|
|
551
563
|
#syncInput() {
|
|
552
564
|
const iso = this.#kind === "date"
|
|
553
565
|
? this.#value.toISODate()
|
|
554
|
-
: this.#
|
|
566
|
+
: this.#kind === "time"
|
|
567
|
+
? this.#value.toISOTime(this.secondsValue)
|
|
568
|
+
: this.#value.toISODateTime(this.secondsValue)
|
|
555
569
|
const next = iso ?? ""
|
|
556
570
|
|
|
557
571
|
if (this.inputTarget.value === next) return
|
|
@@ -289,6 +289,35 @@ export class IncompleteDate {
|
|
|
289
289
|
this.second = match[3] === undefined ? null : Number(match[3])
|
|
290
290
|
return true
|
|
291
291
|
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Fills every field from "YYYY-MM-DDTHH:MM(:SS)" - the datetime-local
|
|
295
|
+
* wire format (no zone, local wall time).
|
|
296
|
+
*
|
|
297
|
+
* @param {string | null} iso
|
|
298
|
+
* @returns {boolean} false (nothing touched) unless both halves parse
|
|
299
|
+
*/
|
|
300
|
+
setFromISODateTime(iso) {
|
|
301
|
+
// A fractional-seconds tail is tolerated (some engines normalize the
|
|
302
|
+
// value with one) and dropped: the field edits whole seconds.
|
|
303
|
+
const match = /^(\d{4}-\d{2}-\d{2})T(\d{2}:\d{2}(?::\d{2})?)(?:\.\d+)?$/.exec(iso ?? "")
|
|
304
|
+
|
|
305
|
+
if (!match) return false
|
|
306
|
+
|
|
307
|
+
return this.setFromISODate(match[1]) && this.setFromISOTime(match[2])
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* @param {boolean} [withSeconds=false]
|
|
312
|
+
* @returns {string | null} "YYYY-MM-DDTHH:MM(:SS)", or null while either
|
|
313
|
+
* half is incomplete
|
|
314
|
+
*/
|
|
315
|
+
toISODateTime(withSeconds = false) {
|
|
316
|
+
const date = this.toISODate()
|
|
317
|
+
const time = this.toISOTime(withSeconds)
|
|
318
|
+
|
|
319
|
+
return date && time ? `${date}T${time}` : null
|
|
320
|
+
}
|
|
292
321
|
}
|
|
293
322
|
|
|
294
323
|
function clampToLimits(value, min, max) {
|
data/lib/poetry/core/check.rb
CHANGED
|
@@ -796,13 +796,13 @@ module Poetry
|
|
|
796
796
|
end
|
|
797
797
|
end
|
|
798
798
|
|
|
799
|
-
def walk_prism(node, &
|
|
799
|
+
def walk_prism(node, &)
|
|
800
800
|
return unless node
|
|
801
801
|
|
|
802
802
|
yield node
|
|
803
803
|
return unless node.respond_to?(:compact_child_nodes)
|
|
804
804
|
|
|
805
|
-
node.compact_child_nodes.each { |child| walk_prism(child, &
|
|
805
|
+
node.compact_child_nodes.each { |child| walk_prism(child, &) }
|
|
806
806
|
end
|
|
807
807
|
|
|
808
808
|
# The block seam of a setter, both directions. A yieldless
|
|
@@ -1262,11 +1262,11 @@ module Poetry
|
|
|
1262
1262
|
joined.empty? ? nil : joined
|
|
1263
1263
|
end
|
|
1264
1264
|
|
|
1265
|
-
def walk(node, &
|
|
1265
|
+
def walk(node, &)
|
|
1266
1266
|
return unless node
|
|
1267
1267
|
|
|
1268
1268
|
yield node
|
|
1269
|
-
node.child_nodes.compact.each { |child| walk(child, &
|
|
1269
|
+
node.child_nodes.compact.each { |child| walk(child, &) } if node.respond_to?(:child_nodes)
|
|
1270
1270
|
end
|
|
1271
1271
|
end
|
|
1272
1272
|
|
|
@@ -75,9 +75,9 @@ module Poetry
|
|
|
75
75
|
"the herb gem is required for template class extraction - add `gem \"herb\"` to your Gemfile"
|
|
76
76
|
end
|
|
77
77
|
|
|
78
|
-
def walk(node, &
|
|
78
|
+
def walk(node, &)
|
|
79
79
|
yield node
|
|
80
|
-
node.child_nodes.compact.each { |child| walk(child, &
|
|
80
|
+
node.child_nodes.compact.each { |child| walk(child, &) } if node.respond_to?(:child_nodes)
|
|
81
81
|
end
|
|
82
82
|
|
|
83
83
|
def class_attribute?(attribute)
|
|
@@ -408,9 +408,9 @@ module Poetry
|
|
|
408
408
|
end
|
|
409
409
|
end
|
|
410
410
|
|
|
411
|
-
def each_table(node, &
|
|
411
|
+
def each_table(node, &)
|
|
412
412
|
yield node if node.element? && node.tag == "table"
|
|
413
|
-
node.children.each { |child| each_table(child, &
|
|
413
|
+
node.children.each { |child| each_table(child, &) }
|
|
414
414
|
end
|
|
415
415
|
|
|
416
416
|
def collect_badge_variants(node, variants)
|
|
@@ -15,10 +15,16 @@ module Poetry
|
|
|
15
15
|
#
|
|
16
16
|
# @api private
|
|
17
17
|
class SkillText < LlmsText
|
|
18
|
-
|
|
18
|
+
# @param registry [Registry] the component registry the skill describes
|
|
19
|
+
# @param families [Hash{String => Array<String>}] family name => component names
|
|
20
|
+
# @param charts_registry [Registry, nil] the charts registry, when the gem is present
|
|
21
|
+
# @param builder_family [String] the family whose reference carries the registry's
|
|
22
|
+
# form_builder section (rules, method summaries, the f.input as: vocabulary)
|
|
23
|
+
def initialize(registry:, families:, charts_registry: nil, builder_family: "forms")
|
|
19
24
|
super(registry: registry)
|
|
20
25
|
@families = families
|
|
21
26
|
@charts_registry = charts_registry
|
|
27
|
+
@builder_family = builder_family
|
|
22
28
|
end
|
|
23
29
|
|
|
24
30
|
# The installable file map, paths relative to .claude/skills/poetry/.
|
|
@@ -250,9 +256,32 @@ module Poetry
|
|
|
250
256
|
not suggestions. Options are keywords; content is the block.
|
|
251
257
|
|
|
252
258
|
#{sections(paths)}
|
|
259
|
+
#{builder_reference if family == @builder_family}
|
|
253
260
|
MD
|
|
254
261
|
end
|
|
255
262
|
|
|
263
|
+
# The registry's form_builder section rides the family that hosts the
|
|
264
|
+
# form controls: the builder's RULE lines, its method summaries, and
|
|
265
|
+
# the f.input as: vocabulary. A registry without one (charts, a host
|
|
266
|
+
# without the UI gem) contributes nothing.
|
|
267
|
+
def builder_reference
|
|
268
|
+
section = @registry.respond_to?(:form_builder) ? @registry.form_builder : nil
|
|
269
|
+
return "" unless section.is_a?(Hash) && section.any?
|
|
270
|
+
|
|
271
|
+
out = ["## Form builder (model-bound forms)", "",
|
|
272
|
+
"Inside `form_with(model:, builder:)` forms the builder derives label, hint, error, " \
|
|
273
|
+
"and aria from the model; these `RULE` lines bind there."]
|
|
274
|
+
rules = Array(section["rules"])
|
|
275
|
+
out << "" << rules.map { |rule| "- RULE: #{rule}" }.join("\n") if rules.any?
|
|
276
|
+
methods = section["methods"]
|
|
277
|
+
if methods.is_a?(Hash) && methods.any?
|
|
278
|
+
out << "" << "Methods:" << "" << methods.map { |name, summary| "- `#{name}` - #{summary}" }.join("\n")
|
|
279
|
+
end
|
|
280
|
+
types = Array(section["input_types"])
|
|
281
|
+
out << "" << "`f.input` `as:` values: #{types.join(", ")}" if types.any?
|
|
282
|
+
out.join("\n")
|
|
283
|
+
end
|
|
284
|
+
|
|
256
285
|
# The blocks reference inlines every block's full source - the block
|
|
257
286
|
# WITH source is the load-bearing agent path.
|
|
258
287
|
def blocks_reference
|
|
@@ -86,7 +86,6 @@ module Poetry
|
|
|
86
86
|
# while other data attributes are merged normally.
|
|
87
87
|
#
|
|
88
88
|
# @param hashes [Array<Hash>] One or more stimulus data hashes
|
|
89
|
-
# @param block [Proc] Optional block passed to Hash#merge for custom merge logic
|
|
90
89
|
# @yield [key, existing, incoming] Hash#merge's conflict resolver for
|
|
91
90
|
# every key other than controller and action
|
|
92
91
|
# @yieldreturn [Object] the value to keep
|
|
@@ -97,12 +96,12 @@ module Poetry
|
|
|
97
96
|
# { controller: "dropdown tooltip", target_value: "main" }
|
|
98
97
|
# )
|
|
99
98
|
# # => { controller: "dropdown tooltip", action: "click->dropdown#toggle", target_value: "main" }
|
|
100
|
-
def merge(*hashes, &
|
|
99
|
+
def merge(*hashes, &)
|
|
101
100
|
hashes.flatten.compact.each_with_object({}.with_indifferent_access) do |hash, result|
|
|
102
101
|
hash = hash.deep_dup.with_indifferent_access
|
|
103
102
|
result[:controller] = merge_controllers(result[:controller], hash.delete(:controller))
|
|
104
103
|
result[:action] = merge_actions(result[:action], hash.delete(:action))
|
|
105
|
-
result.merge!(hash, &
|
|
104
|
+
result.merge!(hash, &)
|
|
106
105
|
end
|
|
107
106
|
end
|
|
108
107
|
|
data/lib/poetry/core/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: poetry-core
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.0.
|
|
4
|
+
version: 0.0.3
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Matt Solt
|
|
@@ -73,6 +73,7 @@ executables: []
|
|
|
73
73
|
extensions: []
|
|
74
74
|
extra_rdoc_files: []
|
|
75
75
|
files:
|
|
76
|
+
- ".gitattributes"
|
|
76
77
|
- CHANGELOG.md
|
|
77
78
|
- DESIGN.md
|
|
78
79
|
- LICENSE.txt
|
|
@@ -254,7 +255,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
254
255
|
requirements:
|
|
255
256
|
- - ">="
|
|
256
257
|
- !ruby/object:Gem::Version
|
|
257
|
-
version: 3.
|
|
258
|
+
version: 3.4.0
|
|
258
259
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
259
260
|
requirements:
|
|
260
261
|
- - ">="
|