@pienter/ui 0.23.0 → 0.24.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.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,21 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 0.24.0 - 2026-09-16
6
+ ### Added
7
+
8
+ - `RecordForm` and `RecordFields` gain two built-in field types, so a price
9
+ or a sale window no longer needs a `#field:name` slot repeating label,
10
+ hint, error and id wiring by hand. `{ type: 'number', min?, max?, step?,
11
+ placeholder?, steppers? }` renders `NumberField`; only finite numbers
12
+ display, and clearing the input writes `null` into the record rather than
13
+ `NaN`, since the payload is JSON. `{ type: 'date' | 'datetime-local' |
14
+ 'time', min?, max?, step? }` renders `DateInput` with the ISO string shapes
15
+ it documents. Both honour `busy`, `disabled`, `required` and `readonly`
16
+ like every other built-in and can still be replaced through their slot.
17
+ - `shopping-cart` icon, so a commerce module or webshop entry no longer has
18
+ to borrow `layers`.
19
+
5
20
  ## 0.23.0 - 2026-09-16
6
21
  ### Added
7
22
 
package/CONVENTIONS.md CHANGED
@@ -1545,7 +1545,10 @@ and request state. Index's compact row spacing uses
1545
1545
  RecordForm emits detached JSON snapshots, preserving every supplied property.
1546
1546
  Consumers construct an explicit writable object from backend records. Typed path
1547
1547
  segments identify nested fields and array indices; fields display issues for
1548
- their path and descendants. The form summary retains unmatched issues.
1548
+ their path and descendants. The form summary retains unmatched issues. Built-in
1549
+ field types map one to one onto the form primitives: text, email, textarea,
1550
+ select, checkbox, image, number (NumberField, cleared writes `null`) and
1551
+ date, datetime-local and time (DateInput, ISO strings).
1549
1552
  `--pui-record-form-columns` customizes the field grid, defaulting to one column.
1550
1553
 
1551
1554
  RecordForm's `fields` slot replaces its default RecordFields rendering for
@@ -8,6 +8,8 @@ import TextInput from '../text-input/TextInput.vue';
8
8
  import PuiTextarea from '../textarea/Textarea.vue';
9
9
  import PuiSelect from '../select/Select.vue';
10
10
  import Checkbox from '../checkbox/Checkbox.vue';
11
+ import NumberField from '../number-field/NumberField.vue';
12
+ import DateInput from '../date-input/DateInput.vue';
11
13
  import {
12
14
  readPath,
13
15
  updatePath,
@@ -46,6 +48,21 @@ function textFor(field: RecordFormField): string {
46
48
  const value = valueFor(field);
47
49
  return typeof value === 'string' ? value : '';
48
50
  }
51
+ function numberFor(field: RecordFormField): number | undefined {
52
+ const value = valueFor(field);
53
+ return typeof value === 'number' && Number.isFinite(value)
54
+ ? value
55
+ : undefined;
56
+ }
57
+ function isDateType(
58
+ field: RecordFormField,
59
+ ): field is RecordFormField & { type: 'date' | 'datetime-local' | 'time' } {
60
+ return (
61
+ field.type === 'date' ||
62
+ field.type === 'datetime-local' ||
63
+ field.type === 'time'
64
+ );
65
+ }
49
66
  function altFor(field: RecordFormField): string {
50
67
  const name = field.type === 'image' ? field.altName : undefined;
51
68
  if (!name) return '';
@@ -116,6 +133,41 @@ function update(field: RecordFormField, value: unknown): void {
116
133
  :errors="errors"
117
134
  @update:model-value="update(field, $event)"
118
135
  />
136
+ <NumberField
137
+ v-else-if="field.type === 'number'"
138
+ :label="field.label"
139
+ :name="JSON.stringify(pathFor(field))"
140
+ :model-value="numberFor(field)"
141
+ :min="field.min"
142
+ :max="field.max"
143
+ :step="field.step"
144
+ :steppers="field.steppers"
145
+ :hint="field.hint"
146
+ :required="field.required"
147
+ :disabled="busy || field.disabled"
148
+ :readonly="readonly"
149
+ :placeholder="field.placeholder"
150
+ :errors="errors"
151
+ @update:model-value="
152
+ update(field, Number.isNaN($event) ? null : $event)
153
+ "
154
+ />
155
+ <DateInput
156
+ v-else-if="isDateType(field)"
157
+ :label="field.label"
158
+ :name="JSON.stringify(pathFor(field))"
159
+ :model-value="textFor(field)"
160
+ :type="field.type"
161
+ :min="field.min"
162
+ :max="field.max"
163
+ :step="field.step"
164
+ :hint="field.hint"
165
+ :required="field.required"
166
+ :disabled="busy || field.disabled"
167
+ :readonly="readonly"
168
+ :errors="errors"
169
+ @update:model-value="update(field, $event)"
170
+ />
119
171
  <template v-else-if="field.type === 'image'">
120
172
  <div v-if="readonly" class="pui-field" data-readonly="true">
121
173
  <span class="pui-field__label">{{ field.label }}</span>
@@ -30,5 +30,21 @@ export type RecordFormField = RecordFieldBase &
30
30
  altName?: string;
31
31
  }
32
32
  | { type: 'checkbox' }
33
+ | {
34
+ type: 'number';
35
+ min?: number;
36
+ max?: number;
37
+ step?: number | string;
38
+ placeholder?: string;
39
+ /** NumberField's +/− buttons; on unless set to `false`. */
40
+ steppers?: boolean;
41
+ }
42
+ | {
43
+ type: 'date' | 'datetime-local' | 'time';
44
+ /** Bounds in the ISO shape of the value, as on DateInput. */
45
+ min?: string;
46
+ max?: string;
47
+ step?: number | string;
48
+ }
33
49
  | { type: 'custom' }
34
50
  );
package/icons/index.ts CHANGED
@@ -43,6 +43,7 @@ import tool from './tool.js';
43
43
  import trash from './trash.js';
44
44
  import sidebarLeft from './sidebar-left.js';
45
45
  import sidebarRight from './sidebar-right.js';
46
+ import shoppingCart from './shopping-cart.js';
46
47
 
47
48
  export const icons = {
48
49
  info: info,
@@ -90,6 +91,7 @@ export const icons = {
90
91
  trash: trash,
91
92
  'sidebar-left': sidebarLeft,
92
93
  'sidebar-right': sidebarRight,
94
+ 'shopping-cart': shoppingCart,
93
95
  } as const;
94
96
 
95
97
  export type IconName = keyof typeof icons;
@@ -0,0 +1 @@
1
+ export default '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" stroke="var(--pui-icon-clr, currentColor)" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" viewBox="0 0 24 24"><circle cx="8" cy="21" r="1"/><circle cx="19" cy="21" r="1"/><path d="M2.05 2.05h2l2.66 12.42a2 2 0 0 0 2 1.58h9.78a2 2 0 0 0 1.95-1.57l1.65-7.43H5.12"/></svg>';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pienter/ui",
3
- "version": "0.23.0",
3
+ "version": "0.24.0",
4
4
  "description": "Shared Pienter UI components, styles, icons, and browser utilities.",
5
5
  "type": "module",
6
6
  "license": "MIT",