poetry-ui 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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a7e5a4ac13adb19047199d51de9fc0077d10f3b89d74302fc738a10db528dc63
4
- data.tar.gz: 2100d959fc3e89a3c189b6b8350ca4a22d41eda259a286fb071b898e88c9b56d
3
+ metadata.gz: a79167a3bdfe8283f8e3d6517e9b1a253fabd02df41417c5cafa79ad3fa12ddf
4
+ data.tar.gz: adb7128dde7ad61162336a2126b436e558bcd6bd49217c7347f0f829a8cecacc
5
5
  SHA512:
6
- metadata.gz: 8416f0e954b93265431f796ad2f2f42aea2a68c0f912dc7d6d83f224dc30c0c264a0ece1d806e4a0fca567c558e1a592eaa63e445fb8e25c20f98e1ff870c3dc
7
- data.tar.gz: a92f9fb002bb8eb856c2f9810d6733a6234fe95e72131d60cd8e173f943210b7a616681626afc0a40c3cd90ecdcd1c4da0791722cc2b09574395780a734bba6c
6
+ metadata.gz: 78e59128853d1a15f2041262b1f8d486aeb626b4a0bb5f84cc649dd5c2f9dfe064962be4533de4e0f14a0c56e073daf33fb594e7ebfaa601d49adfb043d5c86a
7
+ data.tar.gz: f847ad9c837ff9f46a89fef1d40548506f0e425ef9f95292d5145fe92f76cfb3736cd750a6790c94535337c6b2223932af1c7e6b9bab275dcf853b7bafb61fab
@@ -0,0 +1,6 @@
1
+ <%# DateField's template verbatim: native input visible (and THE form
2
+ value) until the controller builds segments into the group. %>
3
+ <%= content_tag(:div, **root_attributes.to_attributes) do %>
4
+ <%= content_tag(:span, "", **Poetry::Core::HTML::Attributes.new(group_attributes).to_attributes) %>
5
+ <%= tag.input(**input_attributes.to_attributes) %>
6
+ <% end %>
@@ -0,0 +1,91 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Poetry
4
+ module Ui
5
+ # Segmented date-and-time editors.
6
+ module DateTimeField
7
+ # A segmented date-and-time editor: one control, one value. The form
8
+ # value is a native <input type=datetime-local> submitting local wall
9
+ # time as YYYY-MM-DDTHH:MM (HH:MM:SS with seconds:), and without JS
10
+ # that native input simply renders, so the field always works.
11
+ # Enhanced, the date segments and the time segments share one row in
12
+ # the locale's own order and separators; the locale decides 12- vs
13
+ # 24-hour editing (hour_cycle: pins it). No zone travels on the wire:
14
+ # the value is the wall time the user typed, for the app to place.
15
+ #
16
+ # A DateTimeField is a DateField specialized to a date and a time -
17
+ # segments share the date-field-* part vocabulary.
18
+ #
19
+ # @example
20
+ # render Poetry::Ui::DateTimeField::Component.new(
21
+ # name: "event[starts_at]", label: "Starts", value: "2026-07-13T09:30"
22
+ # )
23
+ class Component < DateField::Component
24
+ # Projected into the registry, llms.txt, and the agent surface.
25
+ AGENT_RULES = [
26
+ "Date-and-time entry is a DateTimeField (poetry_date_time_field / form.datetime_field) - " \
27
+ "never a DateField beside a TimeField, three selects, or a bare input type=datetime-local " \
28
+ "when the design system is in play; params[<name>] is YYYY-MM-DDTHH:MM (with :SS under " \
29
+ "seconds:), with or without JS.",
30
+ "No zone rides the wire: the value is the wall time the user typed on their own clock - the " \
31
+ "app places it (Time.zone.parse in the controller, or the model's zone).",
32
+ "12- vs 24-hour follows the user's locale automatically (the dayPeriod segment appears only " \
33
+ "under twelve-hour cycles); hour_cycle: pins it when a product must."
34
+ ].freeze
35
+
36
+ # EXTENDS DateField's root declaration (extend: true merges into
37
+ # the inherited element) - same date-field controller, two more
38
+ # values; group/input inherit untouched.
39
+ use_stimulus do
40
+ on :root, extend: true do
41
+ controller :date_field do
42
+ value :seconds, true, if: :seconds
43
+ value :hour_cycle, if: -> { hour_cycle.present? }
44
+ end
45
+ end
46
+ end
47
+
48
+ option :seconds, :boolean, default: false,
49
+ doc: "Adds the seconds segment; the wire format becomes YYYY-MM-DDTHH:MM:SS."
50
+ option :hour_cycle, :string, doc: "Pins the hour cycle (h12/h23/h11/h24) instead of the locale's."
51
+
52
+ part "date-time-field", "Root - the controller and the enhanced/disabled surface ride " \
53
+ "here (segments inside share the date-field-* vocabulary)",
54
+ states: {
55
+ "data-enhanced" => "the controller connected and built segments (no JS = the " \
56
+ "native input, visible and styled)",
57
+ "data-disabled" => "disabled: is set",
58
+ "data-invalid" => "invalid: is set (the group wears the destructive ring)"
59
+ }
60
+ part "date-time-field-group", "The bordered segment row - hidden until enhancement, then " \
61
+ "the editing surface (cn-input chrome, focus-within ring)",
62
+ states: {
63
+ "data-disabled" => "disabled: is set (chrome dims, pointer events off)",
64
+ "data-invalid" => "invalid: is set (destructive border + ring)"
65
+ }
66
+ part "date-time-field-input", "The native <input type=datetime-local> - THE form value in " \
67
+ "both modes; tabindex -1 + aria-hidden once segments exist"
68
+
69
+ private
70
+
71
+ def input_type
72
+ "datetime-local"
73
+ end
74
+
75
+ def slot_prefix
76
+ "date-time-field"
77
+ end
78
+
79
+ def iso(candidate)
80
+ candidate.respond_to?(:strftime) ? candidate.strftime("%FT%H:%M#{":%S" if seconds}") : candidate.to_s
81
+ end
82
+
83
+ def placeholder_iso
84
+ return iso(placeholder_value) if placeholder_value.present?
85
+
86
+ "#{Date.current.strftime("%F")}T12:00#{":00" if seconds}"
87
+ end
88
+ end
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Poetry
4
+ module Ui
5
+ module DateTimeField
6
+ # The DateTimeField preview: empty and valued fields, seconds, a
7
+ # pinned 24-hour cycle, disabled, invalid, and a pinned locale.
8
+ class Preview < Poetry::Core::Preview::Base
9
+ # Empty: date placeholders then hour/minute (plus AM/PM under
10
+ # twelve-hour locales) once enhanced; the styled native input before that.
11
+ def default
12
+ render_component(name: "event[starts_at]", label: "Starts")
13
+ end
14
+
15
+ def with_value
16
+ render_component(name: "event[starts_at]", value: "2026-07-13T13:05", label: "Starts")
17
+ end
18
+
19
+ def with_seconds
20
+ render_component(name: "job[run_at]", value: "2026-07-13T09:30:15", seconds: true, label: "Run at")
21
+ end
22
+
23
+ # A pinned 24-hour cycle regardless of the page locale.
24
+ def twenty_four_hour
25
+ render_component(name: "shift[starts_at]", value: "2026-07-13T21:15", hour_cycle: "h23", label: "Shift start")
26
+ end
27
+
28
+ def disabled
29
+ render_component(name: "event[starts_at]", value: "2026-07-13T13:05", disabled: true, label: "Starts")
30
+ end
31
+
32
+ def invalid
33
+ render_component(name: "event[starts_at]", value: "2026-07-13T13:05", invalid: true, label: "Starts")
34
+ end
35
+
36
+ # locale: pins the order, separators, and cycle instead of following the page locale.
37
+ def localized
38
+ render_component(name: "termin[beginn]", value: "2026-07-13T14:30", locale: "de-DE", label: "Beginn")
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Poetry
4
+ module Ui
5
+ module DateTimeField
6
+ # DateField's style verbatim (one segment engine, two components) -
7
+ # including the group/date-field marker name, which the enhanced-
8
+ # input selector keys on. See date_field/style.rb for the rationale.
9
+ class Style < Poetry::Core::Style
10
+ base "group/date-field flex w-full flex-col"
11
+
12
+ element :group,
13
+ "cn-input hidden w-fit min-w-0 select-none items-center tabular-nums " \
14
+ "group-data-[enhanced]/date-field:flex " \
15
+ "focus-within:border-ring focus-within:ring-[3px] focus-within:ring-ring/50 " \
16
+ "data-invalid:border-destructive data-invalid:ring-destructive/20 " \
17
+ "dark:data-invalid:ring-destructive/40 " \
18
+ "data-disabled:pointer-events-none data-disabled:opacity-50 " \
19
+ "[&_[data-slot=date-field-segment]]:rounded-sm " \
20
+ "[&_[data-slot=date-field-segment]]:px-0.5 " \
21
+ "[&_[data-slot=date-field-segment]]:outline-none " \
22
+ "[&_[data-slot=date-field-segment]:focus]:bg-accent " \
23
+ "[&_[data-slot=date-field-segment]:focus]:text-accent-foreground " \
24
+ "[&_[data-slot=date-field-segment][data-placeholder]]:text-muted-foreground " \
25
+ "[&_[data-slot=date-field-literal]]:text-muted-foreground"
26
+ element :input, "group-data-[enhanced]/date-field:sr-only"
27
+ end
28
+ end
29
+ end
30
+ end
@@ -26,8 +26,8 @@ module Poetry
26
26
  "Input or a pair of selects; params[<name>] is HH:MM (HH:MM:SS with seconds:).",
27
27
  "12- vs 24-hour follows the user's locale automatically (the dayPeriod segment " \
28
28
  "appears only under twelve-hour cycles); hour_cycle: pins it when a product must.",
29
- "For a date AND a time, compose a DateField and a TimeField side by side - there " \
30
- "is no datetime component by design."
29
+ "For a date AND a time, use a DateTimeField (poetry_date_time_field / form.datetime_field): " \
30
+ "one control, one datetime-local value."
31
31
  ].freeze
32
32
 
33
33
  # EXTENDS DateField's root declaration (extend: true merges into
@@ -1110,6 +1110,18 @@ module Poetry
1110
1110
  render(Poetry::Ui::TimeField::Component.new(**))
1111
1111
  end
1112
1112
 
1113
+ # A segmented date-and-time editor over a native datetime-local
1114
+ # input: one control, one local wall-time value (no zone on the wire).
1115
+ #
1116
+ # @example
1117
+ # poetry_date_time_field(
1118
+ # name: "event[starts_at]", label: "Starts", value: "2026-07-13T09:30"
1119
+ # )
1120
+ # @see Poetry::Ui::DateTimeField::Component
1121
+ def poetry_date_time_field(**)
1122
+ render(Poetry::Ui::DateTimeField::Component.new(**))
1123
+ end
1124
+
1113
1125
  # A quantity within a known range (disk, seats, strength) - role
1114
1126
  # "meter progressbar" (the two-token fallback); never indeterminate.
1115
1127
  #
@@ -28,12 +28,18 @@ module Poetry
28
28
  "f.association(:company) reflects the association - belongs_to renders a Combobox on " \
29
29
  "the foreign key, has_many the select-all checkbox group on singular_ids.",
30
30
  "Validations become attributes: presence -> aria-required (NEVER native required), " \
31
- "length -> maxlength/minlength, numericality -> min/max/step.",
31
+ "length -> maxlength/minlength, numericality -> min/max/step; f.input(required: true/false) " \
32
+ "overrides the presence inference (aria only).",
32
33
  "Hints/placeholders resolve from poetry_form.* i18n (simple_form.* keys keep working " \
33
34
  "as a fallback); pass hint:/placeholder: to override.",
34
35
  "f.submit renders a poetry Button with the Rails i18n label; f.fieldset(legend:)/" \
35
36
  "f.group lay out sections; boolean f.input renders the horizontal Field " \
36
- "(switch: true -> the setting row)."
37
+ "(switch: true -> the setting row).",
38
+ "Apps on simple_form: add poetry-simple_form instead of rewriting views - " \
39
+ "Poetry::SimpleForm.activate! maps every simple_form type onto this builder (poetry-only " \
40
+ "controls via as: :switch/:slider/:otp/:sensitive/:tag_group/:date_picker/:calendar/" \
41
+ ":combobox/:autocomplete/:native_select; poetry: options merge last). The bridge is the " \
42
+ "migration path, form_with(builder:) the end state."
37
43
  ].freeze
38
44
 
39
45
  # One-line summaries of the builder methods, projected into the
@@ -64,7 +70,7 @@ module Poetry
64
70
  {
65
71
  "rules" => AGENT_RULES,
66
72
  "methods" => METHOD_SUMMARIES,
67
- "input_types" => (INPUT_DISPATCH.keys + TYPED_FIELDS + %i[boolean enum]).map(&:to_s)
73
+ "input_types" => (INPUT_DISPATCH.keys + TYPED_FIELDS + %i[boolean switch enum]).map(&:to_s)
68
74
  }
69
75
  end
70
76
 
@@ -251,6 +257,27 @@ module Poetry
251
257
  end
252
258
  end
253
259
 
260
+ # A Field-wrapped DateTimeField (one control, one datetime-local value; seconds:/hour_cycle: pass through).
261
+ #
262
+ # @param method [Symbol] the model attribute
263
+ # @param hint [String, nil] hint text
264
+ # @return [ActiveSupport::SafeBuffer]
265
+ def datetime_field(method, hint: nil, **options)
266
+ field_component = field_for(method, hint: hint)
267
+ describedby = field_component.control_attributes["aria-describedby"]
268
+ @template.render(field_component) do
269
+ @template.render DateTimeField::Component.new(
270
+ name: field_name(method),
271
+ value: object.public_send(method),
272
+ required: required?(method),
273
+ invalid: field_component.invalid?,
274
+ id: field_component.control_attributes["id"],
275
+ **(describedby ? { described_by: describedby } : {}),
276
+ **options.transform_keys(&:to_sym)
277
+ )
278
+ end
279
+ end
280
+
254
281
  # form.file_input(:document) / form.file_input(:photos, variant: :dropzone,
255
282
  # multiple: true) - a Field wrapping a FileInput; the native
256
283
  # input is the form value, so ActiveStorage attaches as usual.
@@ -354,13 +381,14 @@ module Poetry
354
381
  time: :time_field, file: :file_input, sensitive: :sensitive_input,
355
382
  select: :poetry_select, combobox: :poetry_combobox,
356
383
  radio_group: :radio_group, autocomplete: :autocomplete,
357
- tag_group: :tag_group, date_picker: :date_picker, calendar: :calendar
384
+ tag_group: :tag_group, date_picker: :date_picker, calendar: :calendar,
385
+ slider: :slider, otp: :otp_field, native_select: :native_select, datetime: :datetime_field
358
386
  }.freeze
359
387
  # The as: values that answer with #field carrying a native input type.
360
388
  TYPED_FIELDS = %i[email url tel].freeze
361
389
  # The as: values whose dispatch method takes the collection as its
362
390
  # second positional argument.
363
- COLLECTION_ARGS = %i[select combobox radio_group autocomplete].freeze
391
+ COLLECTION_ARGS = %i[select combobox radio_group autocomplete native_select].freeze
364
392
 
365
393
  # The inferred entrypoint: one call, everything derived from the
366
394
  # model. The control type comes from as: when given, otherwise from
@@ -379,34 +407,38 @@ module Poetry
379
407
  #
380
408
  # @param method [Symbol] the model attribute
381
409
  # @param as [Symbol, nil] explicit control type - an INPUT_DISPATCH
382
- # key, :email/:url/:tel, :boolean, or :enum (overrides inference)
410
+ # key, :email/:url/:tel, :boolean, :switch, or :enum (overrides inference)
383
411
  # @param collection [Enumerable, nil] choices for collection-shaped
384
- # types (:select, :combobox, :radio_group, :autocomplete)
412
+ # types (:select, :combobox, :radio_group, :autocomplete, :native_select)
385
413
  # @param hint [String, nil] hint text (overrides the i18n chain)
414
+ # @param required [Boolean, nil] overrides the presence-validator inference for this
415
+ # call (aria-required only, never the native attribute)
386
416
  # @return [ActiveSupport::SafeBuffer] the rendered Field
387
- def input(method, as: nil, collection: nil, hint: nil, **options)
417
+ def input(method, as: nil, collection: nil, hint: nil, required: nil, **options)
418
+ unless required.nil?
419
+ return with_required_override(method, required) do
420
+ input(method, as: as, collection: collection, hint: hint, **options)
421
+ end
422
+ end
423
+
388
424
  type = as || infer_input_type(method, collection: collection)
389
425
  hint ||= form_i18n(:hints, method)
390
426
  options[:placeholder] = form_i18n(:placeholders, method) if options[:placeholder].nil?
391
427
  options.compact!
392
428
 
393
429
  return boolean_input(method, hint: hint, **options) if type == :boolean
430
+ return boolean_input(method, hint: hint, switch: true, **options) if type == :switch
394
431
  return enum_input(method, hint: hint, **options) if type == :enum
395
432
  if TYPED_FIELDS.include?(type)
396
433
  return field(method, hint: hint, type: type, **length_attributes(method),
397
434
  **options)
398
435
  end
399
436
 
400
- if type == :datetime
401
- raise ArgumentError, "f.input cannot infer :datetime (no composite control yet) - " \
402
- "pass as: :date or as: :time explicitly"
403
- end
404
-
405
437
  options = length_attributes(method).merge(options) if %i[string password text].include?(type)
406
438
  options = numeric_attributes(method).merge(options) if type == :number
407
439
  dispatch = INPUT_DISPATCH.fetch(type) do
408
440
  raise ArgumentError, "f.input does not know as: #{type.inspect} " \
409
- "(one of #{INPUT_DISPATCH.keys.inspect}, :email, :url, :tel, :boolean, :enum)"
441
+ "(one of #{INPUT_DISPATCH.keys.inspect}, :email, :url, :tel, :boolean, :switch, :enum)"
410
442
  end
411
443
  args = COLLECTION_ARGS.include?(type) ? [method, collection] : [method]
412
444
  send(dispatch, *args, hint: hint, **options)
@@ -879,6 +911,7 @@ module Poetry
879
911
  # validators (:if/:unless) never claim required, and :on contexts
880
912
  # must match the record's persistence.
881
913
  def required?(method)
914
+ return @required_overrides[method] if @required_overrides&.key?(method)
882
915
  return false unless object.class.respond_to?(:validators_on)
883
916
 
884
917
  context = object.respond_to?(:persisted?) && object.persisted? ? :update : :create
@@ -891,6 +924,16 @@ module Poetry
891
924
  end
892
925
  end
893
926
 
927
+ # Pins required?(method) for the duration of one render (f.input's
928
+ # required: keyword) - every Field-wrapped path reads required? through
929
+ # field_for, so the override reaches aria-required and nothing else.
930
+ def with_required_override(method, value)
931
+ (@required_overrides ||= {})[method] = value
932
+ yield
933
+ ensure
934
+ @required_overrides&.delete(method)
935
+ end
936
+
894
937
  # The Rails 8 respellings: ActionView 8
895
938
  # aliases textarea/checkbox at ITS class body, so a subclass override
896
939
  # of the old name never reaches them - define both, version-gated.
@@ -33,6 +33,7 @@ poetry/ui/context_menu: A menu of actions revealed by right-clicking an element.
33
33
  poetry/ui/data_table: A table with sorting, row selection, and sticky headers.
34
34
  poetry/ui/date_field: A segmented input for typing a date one part at a time.
35
35
  poetry/ui/date_picker: A date field that opens a calendar popover for selection.
36
+ poetry/ui/date_time_field: A segmented input for typing a date and a time one part at a time.
36
37
  poetry/ui/deferred: A region that lazily loads its content on visibility, with skeleton
37
38
  and error states.
38
39
  poetry/ui/dialog: A window overlaid on the page for content that requires attention.
@@ -3922,6 +3922,156 @@ components:
3922
3922
  'on': input
3923
3923
  - method: inputKeydown
3924
3924
  'on': keydown
3925
+ poetry/ui/date_time_field:
3926
+ class_name: Poetry::Ui::DateTimeField::Component
3927
+ identifier: poetry--ui--date_time_field
3928
+ bem_block: poetry-ui-date_time_field
3929
+ styles: []
3930
+ options:
3931
+ - name: described_by
3932
+ type: string
3933
+ description: Ids for aria-describedby (hint or error text).
3934
+ - name: disabled
3935
+ type: boolean
3936
+ default: false
3937
+ description: Disables the field; the segment group dims and goes inert.
3938
+ - name: hour_cycle
3939
+ type: string
3940
+ description: Pins the hour cycle (h12/h23/h11/h24) instead of the locale's.
3941
+ - name: id
3942
+ type: string
3943
+ description: The native input's DOM id - the Field label target.
3944
+ - name: invalid
3945
+ type: boolean
3946
+ default: false
3947
+ description: Paints the destructive border/ring and sets aria-invalid.
3948
+ - name: label
3949
+ type: string
3950
+ description: Standalone accessible name; inside a form the Field label wires
3951
+ ids instead. Segments announce it themselves.
3952
+ - name: locale
3953
+ type: string
3954
+ description: Pins the field to a locale other than the page's.
3955
+ - name: max
3956
+ type:
3957
+ description: The latest allowed date (Date or ISO string) - rides native constraint
3958
+ validation.
3959
+ - name: min
3960
+ type:
3961
+ description: The earliest allowed date (Date or ISO string) - rides native constraint
3962
+ validation.
3963
+ - name: name
3964
+ type: string
3965
+ required: true
3966
+ description: The form field name - required; the value posts as ISO yyyy-mm-dd
3967
+ with or without JS.
3968
+ - name: placeholder_value
3969
+ type:
3970
+ description: What the first arrow press on an empty segment lands on; defaults
3971
+ to today.
3972
+ - name: readonly
3973
+ type: boolean
3974
+ default: false
3975
+ description: The value shows but cannot be edited.
3976
+ - name: required
3977
+ type: boolean
3978
+ default: false
3979
+ description: Marks the native input required.
3980
+ - name: seconds
3981
+ type: boolean
3982
+ default: false
3983
+ description: Adds the seconds segment; the wire format becomes YYYY-MM-DDTHH:MM:SS.
3984
+ - name: value
3985
+ type:
3986
+ description: Date, or an ISO yyyy-mm-dd string; nil renders empty.
3987
+ slots: []
3988
+ description: A segmented input for typing a date and a time one part at a time.
3989
+ elements:
3990
+ - group
3991
+ - input
3992
+ capsule: c812f47a82a9
3993
+ controllers:
3994
+ - identifier: poetry--core--date-field
3995
+ targets:
3996
+ - group
3997
+ - input
3998
+ values:
3999
+ - hourCycle
4000
+ - labels
4001
+ - locale
4002
+ - placeholder
4003
+ - placeholders
4004
+ - seconds
4005
+ actions:
4006
+ - focusGap
4007
+ - settle
4008
+ events:
4009
+ - poetry:date-field:change
4010
+ agent_rules:
4011
+ - Date-and-time entry is a DateTimeField (poetry_date_time_field / form.datetime_field)
4012
+ - never a DateField beside a TimeField, three selects, or a bare input type=datetime-local
4013
+ when the design system is in play; params[<name>] is YYYY-MM-DDTHH:MM (with
4014
+ :SS under seconds:), with or without JS.
4015
+ - 'No zone rides the wire: the value is the wall time the user typed on their
4016
+ own clock - the app places it (Time.zone.parse in the controller, or the model''s
4017
+ zone).'
4018
+ - '12- vs 24-hour follows the user''s locale automatically (the dayPeriod segment
4019
+ appears only under twelve-hour cycles); hour_cycle: pins it when a product must.'
4020
+ parts:
4021
+ - name: date-time-field
4022
+ description: Root - the controller and the enhanced/disabled surface ride here
4023
+ (segments inside share the date-field-* vocabulary)
4024
+ states:
4025
+ - attr: data-enhanced
4026
+ condition: the controller connected and built segments (no JS = the native
4027
+ input, visible and styled)
4028
+ - attr: data-disabled
4029
+ condition: 'disabled: is set'
4030
+ - attr: data-invalid
4031
+ condition: 'invalid: is set (the group wears the destructive ring)'
4032
+ - name: date-time-field-group
4033
+ description: The bordered segment row - hidden until enhancement, then the editing
4034
+ surface (cn-input chrome, focus-within ring)
4035
+ states:
4036
+ - attr: data-disabled
4037
+ condition: 'disabled: is set (chrome dims, pointer events off)'
4038
+ - attr: data-invalid
4039
+ condition: 'invalid: is set (destructive border + ring)'
4040
+ - name: date-time-field-input
4041
+ description: The native <input type=datetime-local> - THE form value in both
4042
+ modes; tabindex -1 + aria-hidden once segments exist
4043
+ stimulus:
4044
+ - element: root
4045
+ controllers:
4046
+ - identifier: poetry--core--date-field
4047
+ registers: true
4048
+ values:
4049
+ - name: locale
4050
+ conditional: if
4051
+ - name: placeholder
4052
+ - name: labels
4053
+ - name: placeholders
4054
+ - identifier: poetry--core--date-field
4055
+ values:
4056
+ - name: seconds
4057
+ conditional: if seconds
4058
+ - name: hour_cycle
4059
+ conditional: if
4060
+ - element: group
4061
+ controllers:
4062
+ - identifier: poetry--core--date-field
4063
+ targets:
4064
+ - name: group
4065
+ actions:
4066
+ - method: focusGap
4067
+ 'on': click
4068
+ - method: settle
4069
+ 'on': focusout
4070
+ - element: input
4071
+ controllers:
4072
+ - identifier: poetry--core--date-field
4073
+ targets:
4074
+ - name: input
3925
4075
  poetry/ui/deferred:
3926
4076
  class_name: Poetry::Ui::Deferred::Component
3927
4077
  identifier: poetry--ui--deferred
@@ -10672,8 +10822,8 @@ components:
10672
10822
  Input or a pair of selects; params[<name>] is HH:MM (HH:MM:SS with seconds:).
10673
10823
  - '12- vs 24-hour follows the user''s locale automatically (the dayPeriod segment
10674
10824
  appears only under twelve-hour cycles); hour_cycle: pins it when a product must.'
10675
- - For a date AND a time, compose a DateField and a TimeField side by side - there
10676
- is no datetime component by design.
10825
+ - 'For a date AND a time, use a DateTimeField (poetry_date_time_field / form.datetime_field):
10826
+ one control, one datetime-local value.'
10677
10827
  parts:
10678
10828
  - name: time-field
10679
10829
  description: Root - the controller and the enhanced/disabled surface ride here
@@ -12093,6 +12243,7 @@ helper_args:
12093
12243
  poetry_data_table: 0
12094
12244
  poetry_date_field: 0
12095
12245
  poetry_date_picker: 0
12246
+ poetry_date_time_field: 0
12096
12247
  poetry_deferred: 0
12097
12248
  poetry_dialog: 0
12098
12249
  poetry_drawer: 0
@@ -12208,12 +12359,17 @@ form_builder:
12208
12359
  - f.association(:company) reflects the association - belongs_to renders a Combobox
12209
12360
  on the foreign key, has_many the select-all checkbox group on singular_ids.
12210
12361
  - 'Validations become attributes: presence -> aria-required (NEVER native required),
12211
- length -> maxlength/minlength, numericality -> min/max/step.'
12362
+ length -> maxlength/minlength, numericality -> min/max/step; f.input(required:
12363
+ true/false) overrides the presence inference (aria only).'
12212
12364
  - 'Hints/placeholders resolve from poetry_form.* i18n (simple_form.* keys keep working
12213
12365
  as a fallback); pass hint:/placeholder: to override.'
12214
12366
  - 'f.submit renders a poetry Button with the Rails i18n label; f.fieldset(legend:)/f.group
12215
12367
  lay out sections; boolean f.input renders the horizontal Field (switch: true ->
12216
12368
  the setting row).'
12369
+ - 'Apps on simple_form: add poetry-simple_form instead of rewriting views - Poetry::SimpleForm.activate!
12370
+ maps every simple_form type onto this builder (poetry-only controls via as: :switch/:slider/:otp/:sensitive/:tag_group/:date_picker/:calendar/:combobox/:autocomplete/:native_select;
12371
+ poetry: options merge last). The bridge is the migration path, form_with(builder:)
12372
+ the end state.'
12217
12373
  methods:
12218
12374
  input: 'the inferred entrypoint: type from as:/attachments/enums/column/name'
12219
12375
  association: 'reflection-derived: belongs_to -> Combobox(fk), has_many -> checkbox
@@ -12250,8 +12406,13 @@ form_builder:
12250
12406
  - tag_group
12251
12407
  - date_picker
12252
12408
  - calendar
12409
+ - slider
12410
+ - otp
12411
+ - native_select
12412
+ - datetime
12253
12413
  - email
12254
12414
  - url
12255
12415
  - tel
12256
12416
  - boolean
12417
+ - switch
12257
12418
  - enum
@@ -3,6 +3,6 @@
3
3
  module Poetry
4
4
  module Ui
5
5
  # The gem version.
6
- VERSION = "0.0.2"
6
+ VERSION = "0.0.3"
7
7
  end
8
8
  end
data/lib/poetry/ui.rb CHANGED
@@ -45,6 +45,7 @@ module Poetry
45
45
  "file_input" => %w[input icon], # input variant wears Input; the dropzone's upload glyph
46
46
  "date_field" => %w[input], # the pre-enhancement native input wears Input's chrome
47
47
  "time_field" => %w[date_field input], # DateField at hour granularity - one segment engine
48
+ "date_time_field" => %w[date_field input], # DateField with both runs - the same segment engine
48
49
  "meter" => %w[progress], # wears Progress::Style chrome verbatim
49
50
  "search_field" => %w[input input_group button icon], # composes their chrome
50
51
  "tag_group" => %w[icon], # the remove glyph
@@ -60,7 +61,8 @@ module Poetry
60
61
  # only the family it is composing in. The coverage gate fails on any
61
62
  # new component until it is mapped here.
62
63
  SKILL_FAMILIES = {
63
- "forms" => %w[autocomplete button button_group calendar checkbox combobox date_field date_picker field
64
+ "forms" => %w[autocomplete button button_group calendar checkbox combobox date_field date_picker date_time_field
65
+ field
64
66
  field_group field_separator fieldset
65
67
  file_input input input_group input_otp label native_select number_field
66
68
  questionnaire radio_group search_field select sensitive_input slider switch textarea time_field
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: poetry-ui
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 0.0.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Matt Solt
@@ -15,14 +15,14 @@ dependencies:
15
15
  requirements:
16
16
  - - '='
17
17
  - !ruby/object:Gem::Version
18
- version: 0.0.2
18
+ version: 0.0.3
19
19
  type: :runtime
20
20
  prerelease: false
21
21
  version_requirements: !ruby/object:Gem::Requirement
22
22
  requirements:
23
23
  - - '='
24
24
  - !ruby/object:Gem::Version
25
- version: 0.0.2
25
+ version: 0.0.3
26
26
  description: 'Poetry''s components for the AI-native UI component library: accessible,
27
27
  themeable, agent-legible ViewComponents built entirely on poetry-core''s public
28
28
  DSL.'
@@ -141,6 +141,10 @@ files:
141
141
  - app/components/poetry/ui/date_picker/component.rb
142
142
  - app/components/poetry/ui/date_picker/preview.rb
143
143
  - app/components/poetry/ui/date_picker/style.rb
144
+ - app/components/poetry/ui/date_time_field/component.html.erb
145
+ - app/components/poetry/ui/date_time_field/component.rb
146
+ - app/components/poetry/ui/date_time_field/preview.rb
147
+ - app/components/poetry/ui/date_time_field/style.rb
144
148
  - app/components/poetry/ui/deferred/component.html.erb
145
149
  - app/components/poetry/ui/deferred/component.rb
146
150
  - app/components/poetry/ui/deferred/preview.rb