tosijs-ui 1.16.12 → 1.16.14

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
@@ -1,5 +1,58 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.16.14
4
+
5
+ More reach for curated schema forms (#221): into arrays and unions, grouped into sections,
6
+ and usable as a review screen. All of it is off until you ask for it.
7
+
8
+ - **A curation can reach inside arrays and unions.** Give an array's, a union's or an
9
+ object's entry its own `fields`, with paths relative to the property: for an array they
10
+ apply to every item (`thresholds: { fields: { value: 'Threshold' } }`), for a union to the
11
+ branch the value currently matches. Items and branches keep what is not shown, an error in
12
+ an item property that is not shown is listed by its real path, and a union's
13
+ `picker: false` hides the variant picker. One `fields` covers every branch of a union: a
14
+ path the active branch lacks is left out, a path no branch has says so.
15
+ - **Sections.** An entry `{ section: 'Advanced', fields: {...}, collapsed?: true }` groups
16
+ fields in a titled panel. `collapsed: true` also works on an object or array entry. A
17
+ section or collapsed entry is marked `-invalid`, with a red title, while a field inside it
18
+ is in error. **TypeScript:** `FieldCuration`'s values now include `CuratedSection`, so code
19
+ that walks a curation and reads `entry.caption` needs the new guard: `isSection` is exported.
20
+ - **Proposals, for review screens.** `form.proposals = { path: { value, reason?, source? } }`
21
+ shows each proposed value under its field with Accept and Reject. `value` stays the
22
+ current value until one is accepted; accepting fires `proposalaccept` then `change`,
23
+ rejecting fires `proposalreject`. `acceptProposal(path)` / `rejectProposal(path)` do it
24
+ from code, and `proposalView` renders a proposal your own way. A proposal can be for a
25
+ whole array, object or union, and an object item of an array can be proposed whole
26
+ (`list.0`). One for an array item follows its item when items move. `proposalView` is
27
+ given `ProposalContext.readOnly`, and its `accept` / `reject` do nothing while the form is
28
+ read-only. Works with or without a curation. New localized strings: `Proposed`, `Accept`, `Reject`,
29
+ `Proposed changes to fields not shown here`.
30
+ - For TypeScript: `isSection` is exported; `ProposalContext.readOnly` tells a custom
31
+ `proposalView` that the form is read-only.
32
+ - **Fixed: an array item with several fields laid them out in two columns**, so the second
33
+ field sat in the column meant for the move and remove buttons and the buttons wrapped
34
+ underneath. Fields now stack and the buttons sit beside the first. The form now places an
35
+ item's children itself (`grid-column`), so a stylesheet of your own that arranged
36
+ `.schema-item` children may need a look.
37
+ - **Fixed: setting `value` with a different number of array items left the old rows.** A
38
+ longer array showed only the rows it had before, and a shorter one kept controls for items
39
+ that no longer existed. Arrays are now re-filled when their length changes.
40
+ - **Fixed: a schema `title` that is not a string** was handed to the label, or to a union's
41
+ picker option, as element properties. Only a string is a title now, for properties and for
42
+ `anyOf` / `oneOf` branches; anything else falls back to the key or the branch's value.
43
+ - Schema `title`s were already shown as written, never humanised or localized; the docs now
44
+ say so and a test holds it.
45
+
46
+ ## 1.16.13
47
+
48
+ Documentation only; no library code changed.
49
+
50
+ - **Better examples for the two 1.16.12 features**, in the package's doc comments and
51
+ `llms.txt` as well as on the site. The table's row detail example now opens each invoice to
52
+ a nested `<tosi-table>` of its line items with a totals line. The curated form example
53
+ starts from a valid value and has **View only** and **Every field** toggles; the
54
+ hidden-error case is a separate, smaller example.
55
+
3
56
  ## 1.16.12
4
57
 
5
58
  Two additions for task-focused screens: a table row can open to show a detail element, and a
@@ -1005,41 +1005,65 @@ target `.tr-detail`. Add `Details` to your localized strings.
1005
1005
  import { tosiTable } from 'tosijs-ui'
1006
1006
  import { elements } from 'tosijs'
1007
1007
 
1008
- const { div, table, tr, td, th, b } = elements
1008
+ const { div, span, b } = elements
1009
1009
 
1010
1010
  const invoices = Array.from({ length: 2000 }, (_, i) => ({
1011
1011
  id: `INV-${1000 + i}`,
1012
1012
  buyer: ['Acme', 'Globex', 'Initech', 'Umbrella'][i % 4],
1013
- lines: Array.from({ length: 1 + (i % 4) }, (_, n) => ({
1014
- item: ['Widget', 'Gasket', 'Sprocket', 'Flange'][(i + n) % 4],
1015
- qty: 1 + ((i + n) % 5),
1016
- price: 12.5 * (1 + ((i * 7 + n) % 9)),
1017
- })),
1013
+ lines: Array.from({ length: 1 + (i % 4) }, (_, n) => {
1014
+ const qty = 1 + ((i + n) % 5)
1015
+ const price = 12.5 * (1 + ((i * 7 + n) % 9))
1016
+ return {
1017
+ item: ['Widget', 'Gasket', 'Sprocket', 'Flange'][(i + n) % 4],
1018
+ qty,
1019
+ price,
1020
+ amount: qty * price,
1021
+ }
1022
+ }),
1018
1023
  }))
1019
1024
  for (const invoice of invoices) {
1020
- invoice.total = invoice.lines.reduce((sum, l) => sum + l.qty * l.price, 0)
1025
+ invoice.total = invoice.lines.reduce((sum, line) => sum + line.amount, 0)
1021
1026
  }
1022
1027
 
1028
+ const usd = new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' })
1029
+
1030
+ // The detail is a component built from the row: here, a table of its line items.
1031
+ const invoiceDetail = (invoice) =>
1032
+ div(
1033
+ { class: 'invoice-detail' },
1034
+ tosiTable({
1035
+ rowHeight: 0,
1036
+ nosort: true,
1037
+ nohide: true,
1038
+ noreorder: true,
1039
+ array: invoice.lines,
1040
+ columns: [
1041
+ { prop: 'item', name: 'Item', width: 160 },
1042
+ { prop: 'qty', name: 'Qty', width: 70, type: 'number' },
1043
+ { prop: 'price', name: 'Unit price', width: 110, type: 'currency(USD)' },
1044
+ { prop: 'amount', name: 'Amount', width: 110, type: 'currency(USD)' },
1045
+ ],
1046
+ }),
1047
+ div(
1048
+ { class: 'invoice-total' },
1049
+ span(`${invoice.lines.length} line${invoice.lines.length === 1 ? '' : 's'}, billed to ${invoice.buyer}`),
1050
+ b('Total ', usd.format(invoice.total))
1051
+ )
1052
+ )
1053
+
1023
1054
  const invoiceTable = tosiTable({
1024
- style: { height: '320px' },
1055
+ style: { height: '360px' },
1025
1056
  rowHeight: 32,
1026
1057
  select: true,
1027
1058
  array: invoices,
1028
1059
  columns: [
1029
1060
  { prop: 'detail', width: 40, expander: true },
1030
1061
  { prop: 'id', name: 'Invoice', width: 120 },
1031
- { prop: 'buyer', width: 160 },
1032
- { prop: 'total', width: 120, type: 'currency(USD)' },
1062
+ { prop: 'buyer', name: 'Buyer', width: 160 },
1063
+ { prop: 'total', name: 'Total', width: 120, type: 'currency(USD)' },
1033
1064
  ],
1034
1065
  rowId: (row) => row.id,
1035
- rowDetail: (row) =>
1036
- table(
1037
- { class: 'invoice-lines' },
1038
- tr(th('Item'), th('Qty'), th('Price')),
1039
- ...row.lines.map((line) =>
1040
- tr(td(line.item), td(String(line.qty)), td(line.price.toFixed(2)))
1041
- )
1042
- ),
1066
+ rowDetail: invoiceDetail,
1043
1067
  })
1044
1068
  invoiceTable.expandedRowIds = ['INV-1001']
1045
1069
 
@@ -1054,12 +1078,13 @@ invoiceTable.addEventListener('rowcollapse', (event) => {
1054
1078
  preview.append(invoiceTable, log)
1055
1079
  ```
1056
1080
  ```css
1057
- .preview .invoice-lines {
1058
- border-collapse: collapse;
1081
+ .preview .invoice-detail {
1082
+ max-width: 480px;
1059
1083
  }
1060
- .preview .invoice-lines :is(td, th) {
1061
- padding: 2px 16px 2px 0;
1062
- text-align: left;
1084
+ .preview .invoice-total {
1085
+ display: flex;
1086
+ justify-content: space-between;
1087
+ padding: 8px 0 0;
1063
1088
  }
1064
1089
  .preview .detail-log {
1065
1090
  padding: 8px 0;
@@ -1088,7 +1113,8 @@ test('a row expands to its detail and collapses again', async () => {
1088
1113
  const toggle = table.getCells(row)[0].querySelector('.row-expander')
1089
1114
  expect(toggle.getAttribute('aria-expanded')).toBe('true')
1090
1115
  const detail = document.getElementById(toggle.getAttribute('aria-controls'))
1091
- expect(detail.textContent).toContain('Qty')
1116
+ expect(detail.textContent).toContain('billed to Umbrella')
1117
+ expect(detail.querySelector('tosi-table').array).toBe(row.lines)
1092
1118
 
1093
1119
  toggle.click()
1094
1120
  expect(table.isRowExpanded(row)).toBe(false)