@shwfed/config 3.0.13 → 3.0.15

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.
Files changed (122) hide show
  1. package/dist/module.json +1 -1
  2. package/dist/module.mjs +20 -1
  3. package/dist/runtime/components/actions/ai-l1.md +1 -1
  4. package/dist/runtime/components/actions/buttons/2026-05-15/com.shwfed.actions.button.event.dispatch/prompt.md +1 -1
  5. package/dist/runtime/components/actions/buttons/2026-05-21/com.shwfed.actions.button.http.download/schema.d.ts +1 -1
  6. package/dist/runtime/components/actions/buttons/2026-05-21/com.shwfed.actions.button.http.download/schema.js +2 -2
  7. package/dist/runtime/components/actions/buttons/2026-07-13/com.shwfed.actions.button.state.write/prompt.md +1 -1
  8. package/dist/runtime/components/actions/buttons/2026-07-13/com.shwfed.actions.button.state.write/runtime.vue +1 -1
  9. package/dist/runtime/components/actions/buttons/2026-07-13/com.shwfed.actions.button.state.write/schema.d.ts +2 -4
  10. package/dist/runtime/components/actions/buttons/2026-07-13/com.shwfed.actions.button.state.write/schema.js +1 -1
  11. package/dist/runtime/components/actions/components/trigger-row-scope.vue +6 -3
  12. package/dist/runtime/components/actions/utils/steps.d.ts +1 -1
  13. package/dist/runtime/components/config/blocks/2026-07-17/com.shwfed.block.invoice.digital/config.d.vue.ts +2 -0
  14. package/dist/runtime/components/config/blocks/2026-07-17/com.shwfed.block.invoice.digital/config.vue +10 -2
  15. package/dist/runtime/components/config/blocks/2026-07-17/com.shwfed.block.invoice.digital/config.vue.d.ts +2 -0
  16. package/dist/runtime/components/config/blocks/2026-07-17/com.shwfed.block.invoice.digital/runtime.d.vue.ts +2 -0
  17. package/dist/runtime/components/config/blocks/2026-07-17/com.shwfed.block.invoice.digital/runtime.vue.d.ts +2 -0
  18. package/dist/runtime/components/config/blocks/2026-07-17/com.shwfed.block.invoice.digital/schema.d.ts +1 -0
  19. package/dist/runtime/components/config/blocks/2026-07-17/com.shwfed.block.invoice.digital/schema.js +5 -0
  20. package/dist/runtime/components/config/blocks/2026-07-17/com.shwfed.block.invoice.realestate.lease/config.d.vue.ts +96 -0
  21. package/dist/runtime/components/config/blocks/2026-07-17/com.shwfed.block.invoice.realestate.lease/config.vue +385 -0
  22. package/dist/runtime/components/config/blocks/2026-07-17/com.shwfed.block.invoice.realestate.lease/config.vue.d.ts +96 -0
  23. package/dist/runtime/components/config/blocks/2026-07-17/com.shwfed.block.invoice.realestate.lease/items-colgroup.d.vue.ts +3 -0
  24. package/dist/runtime/components/config/blocks/2026-07-17/com.shwfed.block.invoice.realestate.lease/items-colgroup.vue +20 -0
  25. package/dist/runtime/components/config/blocks/2026-07-17/com.shwfed.block.invoice.realestate.lease/items-colgroup.vue.d.ts +3 -0
  26. package/dist/runtime/components/config/blocks/2026-07-17/com.shwfed.block.invoice.realestate.lease/runtime.d.vue.ts +96 -0
  27. package/dist/runtime/components/config/blocks/2026-07-17/com.shwfed.block.invoice.realestate.lease/runtime.vue +177 -0
  28. package/dist/runtime/components/config/blocks/2026-07-17/com.shwfed.block.invoice.realestate.lease/runtime.vue.d.ts +96 -0
  29. package/dist/runtime/components/config/blocks/2026-07-17/com.shwfed.block.invoice.realestate.lease/schema.d.ts +102 -0
  30. package/dist/runtime/components/config/blocks/2026-07-17/com.shwfed.block.invoice.realestate.lease/schema.js +186 -0
  31. package/dist/runtime/components/config/schema.js +2 -2
  32. package/dist/runtime/components/form/fields/2026-04-24/com.shwfed.form.field.combobox.single/config.d.vue.ts +16 -16
  33. package/dist/runtime/components/form/fields/2026-04-24/com.shwfed.form.field.combobox.single/config.vue.d.ts +16 -16
  34. package/dist/runtime/components/form/fields/2026-04-27/com.shwfed.form.field.daterange/config.d.vue.ts +6 -6
  35. package/dist/runtime/components/form/fields/2026-04-27/com.shwfed.form.field.daterange/config.vue.d.ts +6 -6
  36. package/dist/runtime/components/form/fields/2026-04-27/com.shwfed.form.field.datetimerange/config.d.vue.ts +8 -8
  37. package/dist/runtime/components/form/fields/2026-04-27/com.shwfed.form.field.datetimerange/config.vue.d.ts +8 -8
  38. package/dist/runtime/components/form/fields/2026-04-27/com.shwfed.form.field.timerange/config.d.vue.ts +2 -2
  39. package/dist/runtime/components/form/fields/2026-04-27/com.shwfed.form.field.timerange/config.vue.d.ts +2 -2
  40. package/dist/runtime/components/form/fields/2026-05-13/com.shwfed.form.field.combobox.single.remote/config.d.vue.ts +16 -16
  41. package/dist/runtime/components/form/fields/2026-05-13/com.shwfed.form.field.combobox.single.remote/config.vue.d.ts +16 -16
  42. package/dist/runtime/components/form/fields/2026-05-24/com.shwfed.form.field.monthrange/config.d.vue.ts +6 -6
  43. package/dist/runtime/components/form/fields/2026-05-24/com.shwfed.form.field.monthrange/config.vue.d.ts +6 -6
  44. package/dist/runtime/components/form/fields/2026-05-25/com.shwfed.form.field.combobox.multi/config.d.vue.ts +16 -16
  45. package/dist/runtime/components/form/fields/2026-05-25/com.shwfed.form.field.combobox.multi/config.vue.d.ts +16 -16
  46. package/dist/runtime/components/form/fields/2026-06-09/com.shwfed.form.field.upload/prompt.md +1 -1
  47. package/dist/runtime/components/form/fields/2026-07-18/com.shwfed.form.field.richtext/RichTextEditor.d.vue.ts +33 -0
  48. package/dist/runtime/components/form/fields/2026-07-18/com.shwfed.form.field.richtext/RichTextEditor.vue +269 -0
  49. package/dist/runtime/components/form/fields/2026-07-18/com.shwfed.form.field.richtext/RichTextEditor.vue.d.ts +33 -0
  50. package/dist/runtime/components/form/fields/2026-07-18/com.shwfed.form.field.richtext/config.d.vue.ts +107 -0
  51. package/dist/runtime/components/form/fields/2026-07-18/com.shwfed.form.field.richtext/config.vue +370 -0
  52. package/dist/runtime/components/form/fields/2026-07-18/com.shwfed.form.field.richtext/config.vue.d.ts +107 -0
  53. package/dist/runtime/components/form/fields/2026-07-18/com.shwfed.form.field.richtext/extensions.d.ts +27 -0
  54. package/dist/runtime/components/form/fields/2026-07-18/com.shwfed.form.field.richtext/extensions.js +83 -0
  55. package/dist/runtime/components/form/fields/2026-07-18/com.shwfed.form.field.richtext/features.d.ts +21 -0
  56. package/dist/runtime/components/form/fields/2026-07-18/com.shwfed.form.field.richtext/features.js +16 -0
  57. package/dist/runtime/components/form/fields/2026-07-18/com.shwfed.form.field.richtext/indent.d.ts +26 -0
  58. package/dist/runtime/components/form/fields/2026-07-18/com.shwfed.form.field.richtext/indent.js +64 -0
  59. package/dist/runtime/components/form/fields/2026-07-18/com.shwfed.form.field.richtext/prompt.md +33 -0
  60. package/dist/runtime/components/form/fields/2026-07-18/com.shwfed.form.field.richtext/runtime.d.vue.ts +8 -0
  61. package/dist/runtime/components/form/fields/2026-07-18/com.shwfed.form.field.richtext/runtime.vue +121 -0
  62. package/dist/runtime/components/form/fields/2026-07-18/com.shwfed.form.field.richtext/runtime.vue.d.ts +8 -0
  63. package/dist/runtime/components/form/fields/2026-07-18/com.shwfed.form.field.richtext/schema.d.ts +133 -0
  64. package/dist/runtime/components/form/fields/2026-07-18/com.shwfed.form.field.richtext/schema.js +65 -0
  65. package/dist/runtime/components/operations/2026-06-11/com.shwfed.operation.http.request.json/runtime.js +16 -4
  66. package/dist/runtime/components/operations/2026-06-11/com.shwfed.operation.http.request.json/schema.js +10 -3
  67. package/dist/runtime/components/operations/2026-06-15/com.shwfed.operation.http.download/schema.js +2 -2
  68. package/dist/runtime/components/operations/2026-06-15/com.shwfed.operation.navigation/schema.js +1 -1
  69. package/dist/runtime/components/operations/utils/resolve.d.ts +1 -1
  70. package/dist/runtime/components/operations/utils/step-vars.d.ts +16 -4
  71. package/dist/runtime/components/operations/utils/step-vars.js +14 -1
  72. package/dist/runtime/components/table/columns/2026-05-27/com.shwfed.table.column.tree-combobox-multi/config.d.vue.ts +2 -2
  73. package/dist/runtime/components/table/columns/2026-05-27/com.shwfed.table.column.tree-combobox-multi/config.vue.d.ts +2 -2
  74. package/dist/runtime/components/table/columns/2026-05-27/com.shwfed.table.column.tree-combobox-single/config.d.vue.ts +2 -2
  75. package/dist/runtime/components/table/columns/2026-05-27/com.shwfed.table.column.tree-combobox-single/config.vue.d.ts +2 -2
  76. package/dist/runtime/components/table/columns/2026-05-28/com.shwfed.table.column.tree-combobox-multi/config.d.vue.ts +2 -2
  77. package/dist/runtime/components/table/columns/2026-05-28/com.shwfed.table.column.tree-combobox-multi/config.vue.d.ts +2 -2
  78. package/dist/runtime/components/table/columns/2026-05-28/com.shwfed.table.column.tree-combobox-single/config.d.vue.ts +2 -2
  79. package/dist/runtime/components/table/columns/2026-05-28/com.shwfed.table.column.tree-combobox-single/config.vue.d.ts +2 -2
  80. package/dist/runtime/components/table/columns/2026-06-14/com.shwfed.table.column.tree-combobox-multi/config.d.vue.ts +2 -2
  81. package/dist/runtime/components/table/columns/2026-06-14/com.shwfed.table.column.tree-combobox-multi/config.vue.d.ts +2 -2
  82. package/dist/runtime/components/table/columns/2026-06-14/com.shwfed.table.column.tree-combobox-single/config.d.vue.ts +2 -2
  83. package/dist/runtime/components/table/columns/2026-06-14/com.shwfed.table.column.tree-combobox-single/config.vue.d.ts +2 -2
  84. package/dist/runtime/components/table/columns/2026-07-16/com.shwfed.table.column.upload/prompt.md +1 -1
  85. package/dist/runtime/components/table/columns/2026-07-16/com.shwfed.table.column.upload/runtime.vue +8 -2
  86. package/dist/runtime/components/ui/calendar/Calendar.d.vue.ts +8 -4
  87. package/dist/runtime/components/ui/calendar/Calendar.vue +39 -10
  88. package/dist/runtime/components/ui/calendar/Calendar.vue.d.ts +8 -4
  89. package/dist/runtime/components/ui/date-picker/DatePicker.vue +3 -1
  90. package/dist/runtime/components/ui/date-picker/DatePickerDatePanel.vue +6 -0
  91. package/dist/runtime/components/ui/date-picker/DatePickerDateTimePanel.vue +6 -0
  92. package/dist/runtime/components/ui/date-picker/DatePickerInput.d.vue.ts +1 -1
  93. package/dist/runtime/components/ui/date-picker/DatePickerInput.vue +7 -9
  94. package/dist/runtime/components/ui/date-picker/DatePickerInput.vue.d.ts +1 -1
  95. package/dist/runtime/components/ui/date-picker/DatePickerTimeInput.d.vue.ts +1 -1
  96. package/dist/runtime/components/ui/date-picker/DatePickerTimeInput.vue.d.ts +1 -1
  97. package/dist/runtime/components/ui/date-range-picker/DateRangePicker.vue +10 -7
  98. package/dist/runtime/components/ui/date-range-picker/DateRangePickerInput.d.vue.ts +3 -5
  99. package/dist/runtime/components/ui/date-range-picker/DateRangePickerInput.vue +28 -26
  100. package/dist/runtime/components/ui/date-range-picker/DateRangePickerInput.vue.d.ts +3 -5
  101. package/dist/runtime/components/ui/date-range-picker/DateRangePickerTimeInput.d.vue.ts +2 -2
  102. package/dist/runtime/components/ui/date-range-picker/DateRangePickerTimeInput.vue.d.ts +2 -2
  103. package/dist/runtime/components/ui/expression-editor/picker-entries.js +1 -1
  104. package/dist/runtime/components/ui/expression-editor/step-chip-extension.js +1 -1
  105. package/dist/runtime/components/ui/expression-editor/step-refs.js +1 -1
  106. package/dist/runtime/components/ui/range-calendar/RangeCalendar.d.vue.ts +5 -5
  107. package/dist/runtime/components/ui/range-calendar/RangeCalendar.vue +22 -5
  108. package/dist/runtime/components/ui/range-calendar/RangeCalendar.vue.d.ts +5 -5
  109. package/dist/runtime/components/ui/range-calendar/RangeCalendarNextButton.vue +0 -1
  110. package/dist/runtime/components/ui/range-calendar/RangeCalendarPrevButton.vue +0 -1
  111. package/dist/runtime/share/event-bus.d.ts +16 -0
  112. package/dist/runtime/share/event-bus.js +18 -13
  113. package/dist/runtime/share/idempotency-key.d.ts +18 -0
  114. package/dist/runtime/share/idempotency-key.js +9 -0
  115. package/dist/runtime/share/slot-renderer.vue +83 -24
  116. package/dist/runtime/utils/cel-context.d.ts +3 -2
  117. package/dist/runtime/utils/cel-context.js +1 -0
  118. package/dist/runtime/vendor/cel-js/CLAUDE.md +3 -1
  119. package/dist/runtime/vendor/cel-js/PROMPT.md +31 -2
  120. package/dist/runtime/vendor/cel-js/lib/functions.js +28 -1
  121. package/dist/runtime/vendor/cel-js/lib/registry.js +2 -1
  122. package/package.json +8 -1
@@ -20,11 +20,13 @@ A `list.slice(number)` / `list.slice(number, number)` receiver pair has been add
20
20
 
21
21
  A `string.replace(string, string): string` / `string.replace(string, string, number): string` receiver pair has been added (in `functions.ts` beside `string.trim()`), matching the CEL strings-extension `replace`. The two-arg form delegates to JS `String.prototype.replaceAll` (so all non-overlapping occurrences are replaced; an empty `from` interleaves `to`). The three-arg form takes a limit and mirrors Go's `strings.Replace` via the `replaceLimited` helper: `limit < 0` replaces all, `0` replaces none, otherwise the first `limit` occurrences (`"foo".replace("", "_", 2) == "_f_oo"`).
22
22
 
23
+ A `number.format(map): string` receiver method has been added (not from upstream, in `functions.ts` beside the `string(number)` conversion) — renders a number as a display string via `Intl.NumberFormat`. The map argument is passed through verbatim as the second (`options`) argument of `new Intl.NumberFormat('en-US', options)` — the same options object `Number.prototype.toLocaleString(locales, options)` accepts (`minimumFractionDigits`, `maximumFractionDigits`, `useGrouping`, `style`, `currency`, …); the locale is hardcoded to `'en-US'` so grouping renders as a comma and results are deterministic. Two precision-critical details: (1) the value is handed to `.format()` as a **string** (`Decimal.toString()`), not a JS number — `Intl` parses the decimal string at full precision, whereas the `.toNumber()` round-trip would clip past the safe-integer range (this matters for money); the `format` type only admits `number | bigint`, so the call is cast (`fmt.format as (value: string) => string`). (2) The option *values* arrive as `Decimal` (a CEL map literal's `2` is a `Decimal`) and must be unwrapped to JS numbers before `Intl` sees them — `intlOptionsFromMap` walks the map (plain object or host `Map`) and `.toNumber()`s any `Decimal`/`bigint` value, leaving strings/bools; these are small config knobs so their float round-trip is precision-safe. An impossible option combination (e.g. `maximumFractionDigits < minimumFractionDigits`) throws a `RangeError` from the `Intl` constructor, surfaced as `invalid_format_options`. A `dyn` receiver that is a number at runtime resolves too (overload resolution is by runtime type). Tested in `__tests__/number-format.test.ts`.
24
+
23
25
  `evaluate` now returns `Effect<T, ParseError | EvaluationError | TypeError>` instead of a plain value. The entire evaluation pipeline is Effect-based — every AST node's `evaluate` function returns an Effect, and sub-expressions are composed via `Effect.gen` / `yield*`. Promise-based async handling has been removed. User-registered functions may return `Effect` values, which are automatically flatMapped into the evaluation pipeline. Import `Effect` from `effect` and use `Effect.runSync` or `Effect.runPromise` to execute the returned Effect.
24
26
 
25
27
  The `homogeneousAggregateLiterals` and `enableOptionalTypes` environment options have been removed. Aggregate data structures (lists, maps) are always heterogeneous — mixed-type literals are inferred as `list<dyn>` or `map<dyn, dyn>`. Optional types (`.?`, `[?]`, `optional.*` helpers) are always enabled.
26
28
 
27
- The custom `Optional` class has been replaced with Effect's `Option` type (`import { Option } from 'effect'`). Internal helpers `optionalOf(value)` and `optionalValue(opt)` in `optional.js` handle the CEL-specific semantics (treating `undefined` as `None`, throwing `EvaluationError` on missing value access). The optional surface is `optional.of(A): optional<A>` (the lift / `pure` — `of(null)` is `Some(null)`), `optional.ofNullable(A): optional<A>` (the null-tolerant lift, Java-style — `null` becomes `None`), `optional.none()`, `optional.hasValue()`, `optional.value()`, `optional.or(optional)`, `optional.orValue(value)`, plus `optional<A>.flatMap(var, body): optional<B>` (monadic bind), `optional<A>.map(var, body): optional<B>` (the Functor `fmap`), `optional<A>.zip(other, x, y, body): optional<C>` (the applicative `liftA2` / `zipWith`), `optional<A>.filter(var, predicate): optional<A>` (Rust `Option::filter` / Haskell `mfilter`), and the folds `optional<A>.exists(var, predicate): bool` (predicate on `Some`, `false` on `None` — Rust `is_some_and`) and `optional<A>.all(var, predicate): bool` (predicate on `Some`, vacuously `true` on `None` — Scala `forall`) — all local additions (not from upstream). All are variable-binding macros (registered with `ast` arg types so the unbound args stay unevaluated): on `Some` they bind the value(s) and evaluate `body`; on `None` they short-circuit without touching `body`. `flatMap`'s body must itself yield an `optional`; `map`'s body is a plain value that gets re-wrapped in `Some`; `filter` / `exists` / `all` predicates must be `bool` (enforced at type-check when static, at runtime via `invalid_condition_type` otherwise). `map` is the derived `o.flatMap(v, optional.of(body))` promoted to a first-class macro. `zip` is the flattened `a.flatMap(x, b.map(y, body))`: it type-checks `other` in the outer ctx, binds `x` then `y` under two chained overlays, re-wraps the plain body in `Some`, and short-circuits to `None` if either side is `None` (a `None` receiver skips evaluating `other`). Because CEL has no tuple type the transform is fused (there is no useful `optional<pair>` to return), so `zip` is `zipWith`, not Rust/Scala's pair-building `Option.zip`. The short-circuit makes `zip` observationally monadic — harmless for `optional` (a `None` carries no payload) but it would not generalise to an error-accumulating applicative. An `optional.none()` receiver/argument carries a `param` element type; the shared `bindOptionalValueType` helper (exported from `optional.ts`) binds it as `dyn` so a boolean body still type-checks — every binding macro (`map` / `flatMap` / `zip` / `filter` / `exists` / `all`) goes through it (leaking the `param` into the iteration variable was a real bug in `map` / `flatMap`, regression-tested in `optional-extras.test.ts`). `zip` lives in `optional.ts` beside `flatMap`; the two-variable binding is why it is not folded into the shared `map` expander. NOTE: `map`, `filter`, `all`, and `exists` are **registered in `macros.ts`**, not here — the parser resolves a macro by name before types are known, so the optional variants and the list-comprehension variants must share one expander per name that dispatches on the receiver type at type-check (an `optional` receiver → optional semantics; `list`/`map` → comprehension; a `dyn` receiver is deferred to a runtime `Option.isOption` check). `exists_one` and `find` stay list-only.
29
+ The custom `Optional` class has been replaced with Effect's `Option` type (`import { Option } from 'effect'`). Internal helpers `optionalOf(value)` and `optionalValue(opt)` in `optional.js` handle the CEL-specific semantics (treating `undefined` as `None`, throwing `EvaluationError` on missing value access). **Optional field access diverges from CEL**: `#getOptionalField` in `registry.ts` collapses a present-but-`null` field to `None`, where upstream (and cel-go) yield `Some(null)` — so `row.?foo` and `row.items[?0]` are absent-tolerant against backend JSON, in which `null` is overwhelmingly "absent" (without it `row.?foo.orValue([])` returns `null` and a downstream `size()` fails the overload check — a designer-facing footgun, since `.?` reads as the null-safe path). The collapse is **scoped to field access on purpose**: `optionalOf` itself keeps CEL semantics, so `optional.of(null)` is still `Some(null)` and `find` can still match a null element (its `Option` is the only channel for "did it match", so collapsing there would make a hit indistinguishable from a miss — unlike field access, where `has(row.foo)` and plain `row.foo` remain as escape hatches). `URL.query` is unaffected either way: it null-coalesces (`?? undefined`) before `optionalOf`. Pinned in `optional-extras.test.ts`. The optional surface is `optional.of(A): optional<A>` (the lift / `pure` — `of(null)` is `Some(null)`), `optional.ofNullable(A): optional<A>` (the null-tolerant lift, Java-style — `null` becomes `None`), `optional.none()`, `optional.hasValue()`, `optional.value()`, `optional.or(optional)`, `optional.orValue(value)`, plus `optional<A>.flatMap(var, body): optional<B>` (monadic bind), `optional<A>.map(var, body): optional<B>` (the Functor `fmap`), `optional<A>.zip(other, x, y, body): optional<C>` (the applicative `liftA2` / `zipWith`), `optional<A>.filter(var, predicate): optional<A>` (Rust `Option::filter` / Haskell `mfilter`), and the folds `optional<A>.exists(var, predicate): bool` (predicate on `Some`, `false` on `None` — Rust `is_some_and`) and `optional<A>.all(var, predicate): bool` (predicate on `Some`, vacuously `true` on `None` — Scala `forall`) — all local additions (not from upstream). All are variable-binding macros (registered with `ast` arg types so the unbound args stay unevaluated): on `Some` they bind the value(s) and evaluate `body`; on `None` they short-circuit without touching `body`. `flatMap`'s body must itself yield an `optional`; `map`'s body is a plain value that gets re-wrapped in `Some`; `filter` / `exists` / `all` predicates must be `bool` (enforced at type-check when static, at runtime via `invalid_condition_type` otherwise). `map` is the derived `o.flatMap(v, optional.of(body))` promoted to a first-class macro. `zip` is the flattened `a.flatMap(x, b.map(y, body))`: it type-checks `other` in the outer ctx, binds `x` then `y` under two chained overlays, re-wraps the plain body in `Some`, and short-circuits to `None` if either side is `None` (a `None` receiver skips evaluating `other`). Because CEL has no tuple type the transform is fused (there is no useful `optional<pair>` to return), so `zip` is `zipWith`, not Rust/Scala's pair-building `Option.zip`. The short-circuit makes `zip` observationally monadic — harmless for `optional` (a `None` carries no payload) but it would not generalise to an error-accumulating applicative. An `optional.none()` receiver/argument carries a `param` element type; the shared `bindOptionalValueType` helper (exported from `optional.ts`) binds it as `dyn` so a boolean body still type-checks — every binding macro (`map` / `flatMap` / `zip` / `filter` / `exists` / `all`) goes through it (leaking the `param` into the iteration variable was a real bug in `map` / `flatMap`, regression-tested in `optional-extras.test.ts`). `zip` lives in `optional.ts` beside `flatMap`; the two-variable binding is why it is not folded into the shared `map` expander. NOTE: `map`, `filter`, `all`, and `exists` are **registered in `macros.ts`**, not here — the parser resolves a macro by name before types are known, so the optional variants and the list-comprehension variants must share one expander per name that dispatches on the receiver type at type-check (an `optional` receiver → optional semantics; `list`/`map` → comprehension; a `dyn` receiver is deferred to a runtime `Option.isOption` check). `exists_one` and `find` stay list-only.
28
30
 
29
31
  A `list.filterMap(var, body): list<dyn>` macro has been added (not from upstream, in `macros.ts` beside `find`) — Rust's `Iterator::filter_map`. The body evaluates to an `optional<B>` per element; `Some` values are kept (unwrapped), `None`s dropped, yielding `list<B>`. It fuses the map-then-strip-absent pattern (which would otherwise need `.map(...)` to build a `list<optional>` plus a second pass to drop the `None`s) and is the optional-gated complement to the 3-arg `map(var, filter, transform)`, whose gate is a bool predicate. NOTE: it is **not** a comprehension desugar — the accu/ternary primitives gate on a bool, not on `Option` presence — so unlike the 2/3-arg `map` and `filter` it runs a manual fold like `find` (eval receiver → coerce Set/Map/object to an array → per-element `ev.eval(body)` → `ensureOptional` → push the `Some`s). Type-check: the receiver element type comes from list `valueType` / map `keyType` / `dyn` (same `invalid_comprehension_range` contract as `find`); the body must be `optional<B>` when statically typed (via `ensureOptionalType`), and a `dyn` body defers to the runtime `ensureOptional` (matching `flatMap`); the result is `registry.getListType(bindOptionalValueType(bodyType))`. Because the body already returns an `optional`, `filterMap` needs no receiver-type dispatch (there is no `optional<A>.filterMap` — that role is `flatMap`), so it is a single list-only expander. Tested in `__tests__/filter-map-macro.test.ts`.
30
32
 
@@ -91,8 +91,8 @@ time when statically known, at runtime when the source is `dyn`.
91
91
  ```cel
92
92
  obj.field // access field on map/object
93
93
  obj["field"] // bracket access (equivalent)
94
- obj.?field // optional field access (returns optional)
95
- obj[?0] // optional index access (returns optional)
94
+ obj.?field // optional field access (returns optional; null counts as absent)
95
+ obj[?0] // optional index access (returns optional; null counts as absent)
96
96
  ```
97
97
 
98
98
  ## Built-in Functions
@@ -116,6 +116,26 @@ There is no `int()`, `uint()`, or `double()` — `number` is the only numeric
116
116
  type. To produce an integer, round explicitly with `math.trunc` / `math.floor`
117
117
  / `math.ceil` / `math.round`.
118
118
 
119
+ `string(number)` renders a plain number (no grouping). For a **formatted**
120
+ display string — thousands separators, fixed decimal places, currency, percent —
121
+ use `number.format(options)`:
122
+ ```cel
123
+ (1234567).format({}) // "1,234,567" (comma grouping on by default)
124
+ (1234567.5).format({"minimumFractionDigits": 2}) // "1,234,567.50"
125
+ (3.14159).format({"maximumFractionDigits": 2}) // "3.14"
126
+ (1234.5).format({"minimumFractionDigits": 2,
127
+ "maximumFractionDigits": 2}) // "1,234.50" (fixed 2 decimals)
128
+ (1234567).format({"useGrouping": false}) // "1234567" (no separators)
129
+ (1234.5).format({"style": "currency", "currency": "CNY"}) // "CN¥1,234.50"
130
+ (0.42).format({"style": "percent"}) // "42%"
131
+ ```
132
+ The map is exactly the `options` object of JavaScript's
133
+ `Number.prototype.toLocaleString(locales, options)` / `Intl.NumberFormat` — the
134
+ locale is fixed to `en-US` (so grouping renders as a comma). The number is
135
+ formatted at full precision (large integers past the float safe range keep every
136
+ digit). An impossible combination (e.g. `minimumFractionDigits` above
137
+ `maximumFractionDigits`) is an error.
138
+
119
139
  ### Math
120
140
  ```cel
121
141
  math.trunc(3.7) // 3 — drop the fraction (toward zero)
@@ -414,12 +434,21 @@ An `optional<T>` is either present (`Some`) or absent (`None`). They come from
414
434
  (the lift — wraps a value as present), `optional.ofNullable(x)` (like `of`, but
415
435
  `null` lifts to absent), and `optional.none()`.
416
436
 
437
+ `.?` / `[?]` treat a `null` field as absent, so `row.?foo` is `None` whether
438
+ `foo` is missing, `null`, or `row` itself is `null` — reach for `orValue` and
439
+ trust it. (This differs from standard CEL, where a null field is present.) Only
440
+ `null` collapses; `""`, `0` and `false` are ordinary present values.
441
+
417
442
  ```cel
418
443
  optional.of(5) // present, holds 5
419
444
  optional.of(null) // present, holds null (of never inspects the value)
420
445
  optional.ofNullable(row.middleName) // absent when the field is null
421
446
  optional.none() // absent
422
447
 
448
+ row.?foo.orValue([]) // [] when foo is missing OR null
449
+ row.?foo.orValue("-") // "-" when absent; "" stays "" (only null collapses)
450
+ has(row.foo) // true even when foo is null — tells null from missing
451
+
423
452
  o.hasValue() // bool — is it present?
424
453
  o.value() // unwrap — ERRORS if absent
425
454
  o.orValue(default) // unwrap, or `default` when absent
@@ -3,7 +3,7 @@ import { Decimal } from "./decimal.js";
3
3
  import { TYPES, Type } from "./registry.js";
4
4
  import { Option } from "effect";
5
5
  import { register as registerOptional, optionalOf } from "./optional.js";
6
- import { objKeys, arrayFrom } from "./globals.js";
6
+ import { objKeys, arrayFrom, isArray } from "./globals.js";
7
7
  import { TZDate } from "@date-fns/tz";
8
8
  import { startOfMinute, startOfHour, startOfDay, startOfWeek, startOfMonth, startOfYear, endOfMinute, endOfHour, endOfDay, endOfWeek, endOfMonth, endOfYear, addMilliseconds, addSeconds, addMinutes, addHours, addDays, addWeeks, addMonths, addYears, setMilliseconds, setSeconds, setMinutes, setHours, setDate as setDayOfMonth, setMonth, setYear, format as formatDate, differenceInMilliseconds, differenceInSeconds, differenceInMinutes, differenceInHours, differenceInDays, differenceInWeeks, differenceInMonths, differenceInYears } from "date-fns";
9
9
  function toNum(v) {
@@ -87,6 +87,18 @@ export function registerFunctions(registry) {
87
87
  functionOverload("string(bool): string", (v) => `${v}`);
88
88
  functionOverload("string(number): string", (v) => Decimal.from(v).toString());
89
89
  functionOverload("string(bytes): string", (v) => ByteOpts.toUtf8(v));
90
+ functionOverload("number.format(map): string", (v, options) => {
91
+ const intlOptions = intlOptionsFromMap(options);
92
+ let fmt;
93
+ try {
94
+ fmt = new Intl.NumberFormat("en-US", intlOptions);
95
+ } catch (e) {
96
+ throw evaluationError("invalid_format_options", `number.format(): ${e.message}`);
97
+ }
98
+ return fmt.format(
99
+ Decimal.from(v).toString()
100
+ );
101
+ });
90
102
  functionOverload("string.startsWith(string): bool", (a, b) => a.startsWith(b));
91
103
  functionOverload("string.endsWith(string): bool", (a, b) => a.endsWith(b));
92
104
  functionOverload("string.contains(string): bool", (a, b) => a.includes(b));
@@ -440,6 +452,21 @@ export function registerFunctions(registry) {
440
452
  functionOverload("string(URL): string", (v) => v.href);
441
453
  registerOptional(registry);
442
454
  }
455
+ function intlOptionsFromMap(m) {
456
+ const out = {};
457
+ const put = (k, val) => {
458
+ out[String(k)] = val instanceof Decimal ? val.toNumber() : typeof val === "bigint" ? Number(val) : val;
459
+ };
460
+ if (m instanceof Map) {
461
+ for (const [k, val] of m) put(k, val);
462
+ } else if (m !== null && typeof m === "object" && !isArray(m)) {
463
+ const obj = m;
464
+ for (const k of objKeys(obj)) put(k, obj[k]);
465
+ } else {
466
+ throw evaluationError("invalid_argument", "number.format(): options must be a map");
467
+ }
468
+ return out;
469
+ }
443
470
  function stringSize(str) {
444
471
  let count = 0;
445
472
  for (const _ of str) count++;
@@ -180,7 +180,8 @@ export class TypeDeclaration {
180
180
  if (unwrapped === void 0) return Option.none();
181
181
  const type = ev.debugType(unwrapped);
182
182
  try {
183
- return optionalOf(type.fieldLazy(unwrapped, key, ast, ev));
183
+ const value = type.fieldLazy(unwrapped, key, ast, ev);
184
+ return value === null ? Option.none() : optionalOf(value);
184
185
  } catch (e) {
185
186
  if (e instanceof EvaluationError) return Option.none();
186
187
  throw e;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shwfed/config",
3
- "version": "3.0.13",
3
+ "version": "3.0.15",
4
4
  "description": "Configurable UI for SHWFED",
5
5
  "type": "module",
6
6
  "publishConfig": {
@@ -57,6 +57,13 @@
57
57
  "@tailwindcss/vite": "^4.3.0",
58
58
  "@tanstack/vue-table": "^8.21.3",
59
59
  "@tanstack/vue-virtual": "^3.13.24",
60
+ "@tiptap/core": "3.28.0",
61
+ "@tiptap/extension-list": "3.28.0",
62
+ "@tiptap/extension-text-align": "3.28.0",
63
+ "@tiptap/extensions": "^3.28.0",
64
+ "@tiptap/pm": "^3.28.0",
65
+ "@tiptap/starter-kit": "^3.28.0",
66
+ "@tiptap/vue-3": "^3.28.0",
60
67
  "@unovis/ts": "^1.6.5",
61
68
  "@unovis/vue": "^1.6.5",
62
69
  "@vueuse/core": "^14.3.0",