phlex-forms 0.5.0 → 0.6.0

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: b43b29cfcccda75c6a0af1c888d3edd50b824f3c393c9938890355143d787231
4
- data.tar.gz: 8b231701048287d17a7e44b004f6389f5d760e2b5e36750c90f2416dcbfdca89
3
+ metadata.gz: 1cbde20cd42869a04e1114e08575a532ea441763b6317893fef4aa9e28bd3902
4
+ data.tar.gz: ef6910db2fdcc82f01b5b61b50b6433c4e41bee9e72ab0acaae7cc3a12ed7446
5
5
  SHA512:
6
- metadata.gz: 04f12370599fbae631ac6e88345a7e412eb8f0a143eaa20e76978d8e584aeab6d9657b182e8ac7a424b9624b9df7c362a4008a922c586d4669652fa3da67fa60
7
- data.tar.gz: 802635cc20851fbade99760b38c05d6e3e8fb1a67433fedbcb59ff5318e2ef21fb66d202c7e6f0534c530005dfb468c9e4dc67a01fce5b69b3e5a6c8929db18d
6
+ metadata.gz: 0d4b972c08db3453e7ea345bfa8bacacc18416106a12af4dbdac730a6ebd4a634d26dd3d11cff431c383c41e59c1b352c76e008442095a59bd6e144b1d7cd6d3
7
+ data.tar.gz: bff7f0a358de04a2d41fa4f387025428033591e4d3f94fa4834a6aaa25427a8f0c509b9352d1d04d9752f42b2e887f2f05d9d1b8737041cec57ab100c2a391f5
data/CHANGELOG.md CHANGED
@@ -7,7 +7,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
- ## [0.5.0]
10
+ ## [0.6.0] - 2026-11-13
11
+
12
+ ### Added
13
+
14
+ - **An advisory `recommended:` length on the length rule** (#51):
15
+ `validate: { length: { maximum: 50, recommended: 32 } }`, or the same
16
+ options on a model's `validates … length:`, emits
17
+ `data-validations--length-recommended-value`. The counter reads
18
+ `n / recommended` up to it, then `n / maximum` with `text-warning`, and
19
+ `text-error` past the maximum as before. It never adds an error and never
20
+ becomes `maxlength`; the server ignores it. A counter now renders for
21
+ `recommended:` alone, and an inline rule may give `recommended:` without a
22
+ bound. A value that is not a positive Integer below the maximum, or one
23
+ next to an exact `is:`, is ignored.
24
+
25
+ ## [0.5.0] - 2026-10-15
11
26
 
12
27
  ### Upgrading to 0.5
13
28
 
@@ -72,7 +87,24 @@ read one immutable value, and so can Ractors when every setting can be shared
72
87
  plain Ruby host got `NoMethodError` on its first form (and, with finalizing,
73
88
  from `configure`). phlex-forms requires json before phlex-reactive.
74
89
 
75
- ## [0.4.1] and earlier
90
+ ## [0.4.1] - 2026-10-14
91
+
92
+ ### Fixed
93
+
94
+ - **Client-side validators honour the validator's `message:`** (issue #41).
95
+ `validates :code, format: { with: /\A[a-z]*\z/, message: "lowercase only" }`
96
+ used to show the generic "is invalid" in the browser and the specific text
97
+ only after a round trip. The introspector now emits
98
+ `data-validations--<validator>-message-value`: a String as is (`%{count}`
99
+ stays for the browser to fill), a Symbol resolved on the server through
100
+ ActiveModel's lookup chain in the current locale, a Proc skipped (it needs a
101
+ record). Length's `too_long:` / `too_short:` / `wrong_length:` win over
102
+ `message:`, as on the server. Every shipped controller prefers the value and
103
+ falls back to its bundled message; the submit coordinator collects it too. A
104
+ `validate: { presence: { message: "…" } }` / `confirmation: { message: "…" }`
105
+ rules hash now keeps its options (it used to collapse them to `true`).
106
+
107
+ ## [0.4.0] - 2026-09-30
76
108
 
77
109
  ### Upgrading to 0.4
78
110
 
@@ -116,20 +148,10 @@ plus daisyUI's modifier groups). `PhlexForms::ClassMerge` is gone.
116
148
  longer raises `FeatureUnavailable`. The Plain theme is unchanged and still
117
149
  emits zero daisy classes.
118
150
 
151
+ ## [0.3.1] - 2026-09-09
152
+
119
153
  ### Fixed
120
154
 
121
- - **Client-side validators honour the validator's `message:`** (issue #41).
122
- `validates :code, format: { with: /\A[a-z]*\z/, message: "lowercase only" }`
123
- used to show the generic "is invalid" in the browser and the specific text
124
- only after a round trip. The introspector now emits
125
- `data-validations--<validator>-message-value`: a String as is (`%{count}`
126
- stays for the browser to fill), a Symbol resolved on the server through
127
- ActiveModel's lookup chain in the current locale, a Proc skipped (it needs a
128
- record). Length's `too_long:` / `too_short:` / `wrong_length:` win over
129
- `message:`, as on the server. Every shipped controller prefers the value and
130
- falls back to its bundled message; the submit coordinator collects it too. A
131
- `validate: { presence: { message: "…" } }` / `confirmation: { message: "…" }`
132
- rules hash now keeps its options (it used to collapse them to `true`).
133
155
  - **`rich_textarea` forwards its block into `<lexxy-editor>`**: Lexxy configures
134
156
  an editor through CHILD elements — `<lexxy-prompt>` for @mentions,
135
157
  `<lexxy-code-language-picker>` for code blocks — but the component rendered
@@ -146,29 +168,59 @@ plus daisyUI's modifier groups). `PhlexForms::ClassMerge` is gone.
146
168
  pin uploads to whichever host rendered the page first), fully guarded — no
147
169
  Rails dependency is introduced, and a caller-supplied value always wins.
148
170
 
171
+ ## [0.3.0] - 2026-08-12
172
+
173
+ ### Fixed
174
+
149
175
  - **`FileInput` multiple uploads submit an array** (Rails `file_field` parity):
150
176
  with `multiple:` set, the input name now gets `[]` appended (unless already
151
177
  present), for the standalone component, the `Plain` variant, and the form
152
178
  builder path. Without it the browser collapsed a multi-file selection into
153
179
  ONE scalar param, which a host app's `params.expect(attr: [])` silently
154
180
  discarded — uploads no-opped with a success response.
181
+ - **Hidden fields render as bare inputs** (#22): `f.Hidden`, `f.hidden_field`,
182
+ `f.field(..., as: :hidden)` and `f.Input(..., type: :hidden)` went through
183
+ the `:input` theme role, so under the daisy theme a hidden field carried
184
+ `class="input w-full"`, and daisyUI's `display: inline-flex` beat Safari's
185
+ non-`!important` `display: none`: an empty box and a phantom tab stop. They
186
+ now render the new `Forms::Hidden` leaf through a new `:hidden` theme role,
187
+ with no styling classes and no `error:`.
188
+
189
+ ## [0.2.9] - 2026-07-16
190
+
191
+ ### Fixed
192
+
193
+ - **A caller width class stacked with the `w-full` default instead of replacing
194
+ it**: `DelegatedField#width_class` joined `"w-full"` and the caller's
195
+ `class:` verbatim, so `Forms::Select(class: "w-36")` emitted
196
+ `class="... w-full w-36"` and stylesheet source order — not author intent —
197
+ picked the winner (in practice `w-full`, which blew filter selects up to full
198
+ width; zazu#2934). `ClassMerge` gained a width family (anchored `w-*`, so
199
+ `min-w-*`/`max-w-*` still compose) and `width_class` merges through it —
200
+ last width wins, matching the daisyui size/color family semantics.
201
+
202
+ ## [0.2.8] - 2026-07-11
203
+
204
+ ### Added
205
+
206
+ - **`hidden_field`** (#21): a snake_case alias of `f.Hidden` for Rails
207
+ `FormBuilder` migrations, on inline forms and `fields_for` builders. An
208
+ explicit `value:` still wins over the model's value.
209
+
210
+ ## [0.2.7] - 2026-07-11
211
+
212
+ ### Changed
213
+
214
+ - **`tag_field` uses phlex-reactive 0.12.2's escape-hatch sugar**
215
+ (`reactive_tags` / `reactive_filter`) (#20): the wire name is validated at
216
+ render, and the client-side type-ahead filter runs again (under
217
+ phlex-reactive 0.12.x only one of its two selectors was emitted, so it never
218
+ ran).
219
+
220
+ ## [0.2.6] - 2026-07-11
155
221
 
156
222
  ### Added
157
223
 
158
- - **`checkbox_group` — batched checkbox group for array-valued fields** (the
159
- tag/facet-picker shape): `f.checkbox_group(:tag_ids, Tag.all, value: :id,
160
- label: :name, variant: :pill, size: :sm)`, or via field inference
161
- (`f.field :tag_ids, as: :checkbox_group, collection: Tag.all, value: :id`).
162
- Shares one array-valued field name with a leading empty-array hidden field,
163
- derives the checked set from the model's current value, and renders under both
164
- themes. `variant:` (`:stack`/`:inline`/`:pill`) is layout-only, no JS.
165
- - **`checkbox_group` accessible name** (issue #17): the `div[role="group"]` can
166
- now carry an accessible name/description. HTML/ARIA attributes pass straight
167
- through to the group, so the bare verb is named with plain `aria:`
168
- (`aria: { label: "Tags" }` or `aria: { labelledby: "id" }`); through `f.field`
169
- the Control's own visible `label:` / `hint:` get stable ids and the field wires
170
- `aria-labelledby` / `aria-describedby` at them (no duplicate markup). Absent a
171
- name, output is unchanged.
172
224
  - **`checkbox_group` `item_label:`** — the per-item text accessor, so a
173
225
  `f.field(:tags, as: :checkbox_group, label: "Tags", item_label: ->(t){…})`
174
226
  renders a visible group heading (`label:`, via the Control) AND custom item
@@ -177,6 +229,30 @@ plus daisyUI's modifier groups). `PhlexForms::ClassMerge` is gone.
177
229
  item text; on the bare verb `label:` stays the item accessor and `item_label:`
178
230
  is an alias. Absent it, behavior is unchanged.
179
231
 
232
+ ## [0.2.5] - 2026-07-11
233
+
234
+ ### Added
235
+
236
+ - **`checkbox_group` accessible name** (issue #17): the `div[role="group"]` can
237
+ now carry an accessible name/description. HTML/ARIA attributes pass straight
238
+ through to the group, so the bare verb is named with plain `aria:`
239
+ (`aria: { label: "Tags" }` or `aria: { labelledby: "id" }`); through `f.field`
240
+ the Control's own visible `label:` / `hint:` get stable ids and the field wires
241
+ `aria-labelledby` / `aria-describedby` at them (no duplicate markup). Absent a
242
+ name, output is unchanged.
243
+
244
+ ## [0.2.4] - 2026-07-11
245
+
246
+ ### Added
247
+
248
+ - **`checkbox_group` — batched checkbox group for array-valued fields** (the
249
+ tag/facet-picker shape): `f.checkbox_group(:tag_ids, Tag.all, value: :id,
250
+ label: :name, variant: :pill, size: :sm)`, or via field inference
251
+ (`f.field :tag_ids, as: :checkbox_group, collection: Tag.all, value: :id`).
252
+ Shares one array-valued field name with a leading empty-array hidden field,
253
+ derives the checked set from the model's current value, and renders under both
254
+ themes. `variant:` (`:stack`/`:inline`/`:pill`) is layout-only, no JS.
255
+
180
256
  ### Changed
181
257
 
182
258
  - **Client-side validation Stimulus identifiers dropped the `forms--` prefix**:
@@ -191,15 +267,6 @@ plus daisyUI's modifier groups). `PhlexForms::ClassMerge` is gone.
191
267
 
192
268
  ### Fixed
193
269
 
194
- - **A caller width class stacked with the `w-full` default instead of replacing
195
- it**: `DelegatedField#width_class` joined `"w-full"` and the caller's
196
- `class:` verbatim, so `Forms::Select(class: "w-36")` emitted
197
- `class="... w-full w-36"` and stylesheet source order — not author intent —
198
- picked the winner (in practice `w-full`, which blew filter selects up to full
199
- width; zazu#2934). `ClassMerge` gained a width family (anchored `w-*`, so
200
- `min-w-*`/`max-w-*` still compose) and `width_class` merges through it —
201
- last width wins, matching the daisyui size/color family semantics.
202
-
203
270
  - **`f.Radio` / `Field#radio` rendered the model's current value on every radio
204
271
  instead of each radio's own value**: `field_attributes` carried `value:
205
272
  field_value` and was splatted after the explicit positional value, clobbering
@@ -216,6 +283,37 @@ plus daisyUI's modifier groups). `PhlexForms::ClassMerge` is gone.
216
283
  instead of a single `scope[assoc][field]`. It is now treated as a single
217
284
  nested scope; only genuine collections (Enumerable, not Hash) iterate.
218
285
 
286
+ ## [0.2.3] - 2026-07-08
287
+
288
+ ### Added
289
+
290
+ - **`tag_field`** (#7): `f.field :tags, as: :tags, suggestions: [...]`, a
291
+ model-bound tag/chip control composed from phlex-reactive's client-only tag
292
+ primitives. It submits one comma-joined param; `live_tags` validates it
293
+ inside a `live` form.
294
+
295
+ ## [0.2.2] - 2026-07-08
296
+
297
+ ### Fixed
298
+
299
+ - **`Forms::TimeZoneSelect` raised at render time** (#5): `NoMethodError` for
300
+ `time_zone_options_for_select` (called on the `DaisyUI::Select` receiver)
301
+ and `Phlex::ArgumentError: Expected a String` (an HTML-safe buffer passed to
302
+ `safe`). It was broken in 0.2.0 and 0.2.1.
303
+
304
+ ## [0.2.1] - 2026-07-08
305
+
306
+ ### Added
307
+
308
+ - **Rails form-builder compat aliases** (#4): `Forms::Form#object` (`model`),
309
+ `#object_name` (`scope`) and `#[]` (`field_object`), and
310
+ `Forms::FieldsForBuilder#object` / `#[]`, for call sites migrating off an
311
+ ActionView `FormBuilder`.
312
+
313
+ ## [0.2.0] - 2026-07-08
314
+
315
+ ### Added
316
+
219
317
  - **`Forms::Base` declarative form classes**: subclass, declare fields in
220
318
  `#fields` where `self` IS the form (bare `field :email`, no `f.` prefix),
221
319
  render with `render UserForm.new(model: @user)`. Class-level `form_options`
@@ -284,7 +382,9 @@ plus daisyUI's modifier groups). `PhlexForms::ClassMerge` is gone.
284
382
  Pass `as:` explicitly or set `PhlexForms.config.infer_from_model = false`
285
383
  to keep the old rendering.
286
384
 
287
- ### Initial extraction
385
+ ## [0.1.0] - 2026-07-07
386
+
387
+ ### Added
288
388
 
289
389
  - Initial extraction of the `Forms::` Phlex form builder from the Cosmos apps.
290
390
  - Control-first builder API: `f.field :email, label:, hint:, as:, choices:`
data/README.md CHANGED
@@ -359,6 +359,31 @@ f.field :note, validate: { length: { maximum: 30, too_long: "keep it under %{cou
359
359
  Without a `message:`, the bundled messages apply: they ship for `en` / `fr` /
360
360
  `af`; override them page-wide via `window.PhlexForms.messages`.
361
361
 
362
+ The length counter can count against an advisory `recommended:` length below
363
+ the hard `maximum:`. It never adds an error, never becomes `maxlength`, and the
364
+ server ignores it (ActiveModel keeps the unknown option without reading it):
365
+
366
+ ```ruby
367
+ f.field :title, as: :textarea, validate: { length: { maximum: 50, recommended: 32 } }
368
+ validates :title, length: { maximum: 50, recommended: 32 } # the same, from the model
369
+ ```
370
+
371
+ | Length | Counter | Class |
372
+ |---|---|---|
373
+ | up to `recommended` | `n / recommended` | none |
374
+ | past `recommended`, up to `maximum` | `n / maximum` | `text-warning` |
375
+ | past `maximum` | `n / maximum` | `text-error` (and the `too_long` message) |
376
+
377
+ Without a `maximum:`, the counter reads `n / recommended` and turns
378
+ `text-warning` past it. An inline `validate: { length: { recommended: 32 } }`
379
+ needs no bound; a model-level `validates … length:` still needs one, because
380
+ ActiveModel requires it there. A `recommended:` that is not a positive Integer
381
+ below the maximum, or one next to an exact `is:`, is ignored. The counter's
382
+ `text-warning` / `text-error` classes are toggled from the gem's JavaScript, so
383
+ your Tailwind build must include them. The counter comes from these client-side
384
+ rules only: live validation's server round trip (`Forms::Live`) adds none, and
385
+ a live field shows one only when it also carries client-side length rules.
386
+
362
387
  ## Nested attributes, collections & escape valves
363
388
 
364
389
  ```ruby
@@ -3,12 +3,14 @@ import { interpolate } from "phlex_forms/i18n"
3
3
 
4
4
  // Mirrors ActiveModel::Validations::LengthValidator. Reads:
5
5
  // - maximum / minimum / is
6
+ // - recommended (advisory: the counter's target, never an error)
6
7
  // - too-long / too-short / wrong-length messages (win over `message`)
7
8
  // - allow-blank / allow-nil
8
9
  //
9
- // Counter is rendered next to the input when `maximum` is set, so
10
- // the user gets a live "12 / 60" indicator without having to wait
11
- // for blur.
10
+ // Counter is rendered next to the input when `maximum` or `recommended`
11
+ // is set, so the user gets a live "12 / 60" indicator without having to
12
+ // wait for blur. Past `recommended` it counts against `maximum` and turns
13
+ // `text-warning`; past `maximum` it turns `text-error`.
12
14
  export default class extends FieldValidatorController {
13
15
  // Stimulus walks the prototype chain to accumulate `static values`
14
16
  // and `static targets`, so we only declare the validator-specific
@@ -21,6 +23,7 @@ export default class extends FieldValidatorController {
21
23
  maximum: Number,
22
24
  minimum: Number,
23
25
  is: Number,
26
+ recommended: Number,
24
27
  tooLongMessage: String,
25
28
  tooShortMessage: String,
26
29
  wrongLengthMessage: String,
@@ -28,7 +31,7 @@ export default class extends FieldValidatorController {
28
31
 
29
32
  connect() {
30
33
  super.connect()
31
- if (this.hasMaximumValue) {
34
+ if (this.counts) {
32
35
  this.renderCounter()
33
36
  this.element.addEventListener("input", this.updateCounter)
34
37
  }
@@ -36,11 +39,15 @@ export default class extends FieldValidatorController {
36
39
 
37
40
  disconnect() {
38
41
  super.disconnect()
39
- if (this.hasMaximumValue) {
42
+ if (this.counts) {
40
43
  this.element.removeEventListener("input", this.updateCounter)
41
44
  }
42
45
  }
43
46
 
47
+ get counts() {
48
+ return this.hasMaximumValue || this.recommendedLength(null) !== null
49
+ }
50
+
44
51
  check(value) {
45
52
  const length = this.codepointLength(value)
46
53
 
@@ -67,8 +74,35 @@ export default class extends FieldValidatorController {
67
74
  const counter = this.counterElement()
68
75
  if (!counter) return
69
76
  const length = this.codepointLength(this.element.value)
70
- counter.textContent = `${length} / ${this.maximumValue}`
71
- counter.classList.toggle("text-error", length > this.maximumValue)
77
+ const { limit, state, advisory } = this.counterState(length)
78
+ counter.textContent = `${length} / ${limit}`
79
+ // Without a recommended length the counter is exactly 0.5.0's: a caller's
80
+ // own `text-warning` on a pre-rendered counter target is left alone.
81
+ if (advisory) counter.classList.toggle("text-warning", state === "warning")
82
+ counter.classList.toggle("text-error", state === "error")
83
+ }
84
+
85
+ // The counter's denominator and state. `recommended` applies only below
86
+ // `maximum` (the server drops it otherwise; this guards a hand-written
87
+ // attribute): up to it the counter reads "n / recommended", past it
88
+ // "n / maximum" in a warning colour, past `maximum` an error.
89
+ counterState(length) {
90
+ const maximum = this.hasMaximumValue ? this.maximumValue : null
91
+ const recommended = this.recommendedLength(maximum)
92
+
93
+ const advisory = recommended !== null
94
+
95
+ if (maximum !== null && length > maximum) return { limit: maximum, state: "error", advisory }
96
+ if (!advisory) return { limit: maximum, state: null, advisory }
97
+ if (length <= recommended) return { limit: recommended, state: null, advisory }
98
+ return { limit: maximum ?? recommended, state: "warning", advisory }
99
+ }
100
+
101
+ recommendedLength(maximum) {
102
+ if (!this.hasRecommendedValue) return null
103
+ const recommended = this.recommendedValue
104
+ if (!(recommended > 0) || (maximum !== null && recommended >= maximum)) return null
105
+ return recommended
72
106
  }
73
107
 
74
108
  // Ruby's String#length counts Unicode codepoints; JS's String#length
@@ -166,17 +166,32 @@ module Forms
166
166
  attrs[key] = opts[key].to_s if opts.key?(key)
167
167
  end
168
168
 
169
- if opts.key?(:in) || opts.key?(:within)
170
- range = opts[:in] || opts[:within]
169
+ range = opts[:in] || opts[:within]
170
+ if range
171
171
  attrs[:minimum] = range.min.to_s
172
172
  attrs[:maximum] = range.max.to_s
173
173
  end
174
174
 
175
+ attrs[:recommended] = opts[:recommended].to_s if recommended?(opts, range ? range.max : opts[:maximum])
175
176
  attrs[:allow_blank] = "true" if opts[:allow_blank]
176
177
  attrs[:allow_nil] = "true" if opts[:allow_nil]
177
178
  attrs
178
179
  end
179
180
 
181
+ # `recommended:` is advisory (the counter's target, never an error), so
182
+ # it is kept only when it can sit below the hard limit: a positive
183
+ # Integer under a numeric maximum, or any positive Integer when there is
184
+ # no maximum. An exact `is:` length leaves nothing to recommend. Anything
185
+ # else is dropped without raising.
186
+ def recommended?(opts, maximum)
187
+ recommended = opts[:recommended]
188
+ return false unless recommended.is_a?(Integer) && recommended.positive?
189
+ return false if opts.key?(:is)
190
+ return true if maximum.nil?
191
+
192
+ maximum.is_a?(Numeric) && recommended < maximum
193
+ end
194
+
180
195
  def format_attrs(validator)
181
196
  opts = validator.options
182
197
  regex = opts[:with]
@@ -21,6 +21,20 @@ module Forms
21
21
  # acceptance: true,
22
22
  # )
23
23
  class ManualRules
24
+ # ActiveModel's LengthValidator demands a bound, so `length: { recommended: 32 }`
25
+ # alone (an advisory counter with no limit) can't go through `validates`.
26
+ # This no-op validator carries the options instead; the Introspector matches
27
+ # it by its short name, LengthValidator, and it never adds an error.
28
+ module Advisory
29
+ class LengthValidator < ActiveModel::EachValidator
30
+ def validate_each(_record, _attribute, _value); end
31
+ end
32
+ end
33
+
34
+ # The only keys a bound-less advisory rule may carry. Anything else (a
35
+ # misspelled `maxium:`) goes to ActiveModel, which raises for the missing bound.
36
+ ADVISORY_LENGTH_KEYS = %i[recommended allow_blank allow_nil].freeze
37
+
24
38
  def initialize(rules)
25
39
  @rules = rules
26
40
  end
@@ -51,7 +65,11 @@ module Forms
51
65
  when :presence
52
66
  validates :value, presence: opts.is_a?(Hash) ? opts : true if opts
53
67
  when :length
54
- validates :value, length: opts
68
+ if opts.is_a?(Hash) && opts.key?(:recommended) && (opts.keys - ADVISORY_LENGTH_KEYS).empty?
69
+ validates_with Advisory::LengthValidator, opts.merge(attributes: [:value])
70
+ else
71
+ validates :value, length: opts
72
+ end
55
73
  when :format
56
74
  validates :value, format: opts
57
75
  when :numericality
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module PhlexForms
4
- VERSION = "0.5.0"
4
+ VERSION = "0.6.0"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: phlex-forms
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.0
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mikael Henriksson