@umriss-ui/calculation 0.2.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 ADDED
@@ -0,0 +1,71 @@
1
+ # Changes to `@umriss-ui/calculation`
2
+
3
+ This document describes what changes for **callers** of the package: exports,
4
+ props, behaviour. What was worked on in the repository stands in the
5
+ repository's journal (`docs/journal.md`) and in the specs under `.scratch/`.
6
+
7
+ **The numbers.** The middle digit rises when something is added; the last one
8
+ when something is repaired. As long as the first digit is `0`, no number
9
+ promises compatibility — which is why whatever changes existing behaviour stands
10
+ under a heading "Changed" of its own, no matter which digit rose.
11
+
12
+ ---
13
+
14
+ ## 0.2.0 – A chain stands open (Sep. 2026)
15
+
16
+ 0.1.0 was committed in the repository but never published; this is the first
17
+ version on npm.
18
+
19
+ ### Changed
20
+
21
+ - **A chain in view no longer folds.** Every line and every interim stands
22
+ open, as on paper - gross salary, each deduction with its minus, the net
23
+ salary beneath a rule once they all stand. What should show only on request
24
+ is written as a tree in one line: a `<Sum>` held by a `<Minus>` folds like
25
+ any tree and opens beneath its line. A chain that is an operand still folds,
26
+ as a whole, and opens whole.
27
+ - **No disclosure column where nothing can fold**: a calculation of givens
28
+ and chains alone starts its labels at the surface's edge.
29
+ - **The hover band on operands** is half the accent's surface instead of a
30
+ grey, so coloured text in a marked row keeps its contrast.
31
+
32
+ ---
33
+
34
+ ## 0.1.0 – First release (Sep. 2026)
35
+
36
+ A calculation a reader can follow and redo: the package evaluates every
37
+ operation it shows, so what stands on the screen cannot disagree with the
38
+ number. Takes `@umriss-ui/core` `^0.5.0` as a peer, for its wording.
39
+
40
+ ### Added
41
+
42
+ - **`Calculation`** and the **tree** (ADR-0027): `Given`, `Sum`, `Difference`,
43
+ `Product`, `Quotient` and `Ref`, written as nested elements the way the
44
+ derivation reads. Full precision throughout, rounding only on the screen.
45
+ - **The chain** (ADR-0028): `Chain`, `Plus`, `Minus`, `Times`, `DividedBy` and
46
+ `Interim` - a calculation read top to bottom, each operand worked into the
47
+ value before it, ended by an interim. No precedence: a `Times` or
48
+ `DividedBy` stands alone between two named values. Tree and chain mix both
49
+ ways.
50
+ - **Absence and approximation.** A missing given or a division by zero makes
51
+ every dependent quantity absent, with the reason - never zero. Where the
52
+ rounded operands do not give the rounded result, the result is marked "≈".
53
+ - **Assessment and freshness** through core: `target` and `limits` on any
54
+ quantity, `source`, `asOf` and `ages` on a given; the worst verdict inside a
55
+ folded row stands beside it.
56
+ - **The statement**: a surface in the material of the library's tables and
57
+ cards, rows of one height, fixed columns for label, names, operator, number,
58
+ unit and assessment. The outermost statement stands as on paper and closes
59
+ on the Result, underlined twice. Every other derivation is folded and opens
60
+ beneath its row, joined to it by a falling line and closing with
61
+ "= label"; the row never moves. A folded row shows the formula it hides in
62
+ names, or how many operands there are above four. The verdict is core's
63
+ `Badge`; "≈" explains itself in a `Tooltip`. Narrower than 34 rem the
64
+ layout gives the numbers room.
65
+ - **Accessibility**: a nested list, one sentence per row - "Availability
66
+ equals Run time divided by Planned production time, equals 412 min divided
67
+ by 450 min, equals 91.6 percent" - and a disclosure button per derivation.
68
+ - **Development errors** on the first render, each saying which and where: a
69
+ `Ref` to nothing (listing the ids that exist), a circle through references,
70
+ a wrong operand count, a duplicate id, a component of the caller's own
71
+ wrapping an element, and every rule of the chain.
package/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Roman Händler
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR OTHER DEALINGS IN THE SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,113 @@
1
+ # @umriss-ui/calculation
2
+
3
+ A calculation a reader can follow and redo. A plant screen shows a figure — an
4
+ OEE of 82 %, a cost per piece — and this package shows how it came about, as a
5
+ statement of account: the operator before each number, the final result above a
6
+ double rule, and every figure opening beneath itself into the calculation it
7
+ came from, down to the numbers at its root. The package **performs every operation it
8
+ shows**, so what stands on the screen cannot disagree with the number.
9
+
10
+ ## Install
11
+
12
+ ```bash
13
+ pnpm add @umriss-ui/calculation @umriss-ui/core
14
+ ```
15
+
16
+ `@umriss-ui/core` is a **peer dependency**: the calculation takes its styling
17
+ regime, formats, wording, assessment and freshness from there. React 18 or 19
18
+ as a peer as well.
19
+
20
+ ## Two forms, written as they are shown
21
+
22
+ A **tree** suits a figure put together from factors. Operators are elements,
23
+ their children are their operands in order, givens are the leaves (ADR-0027). A
24
+ quantity used twice is defined once and stands elsewhere as a `<Ref>`:
25
+
26
+ ```tsx
27
+ import { Calculation, Difference, Given, Product, Quotient, Ref } from "@umriss-ui/calculation";
28
+
29
+ <Calculation aria-label="OEE, early shift">
30
+ <Product label="OEE" format="percent" target={0.85}>
31
+ <Quotient label="Availability" format="percent">
32
+ <Difference id="runtime" label="Run time" unit="min">
33
+ <Given id="planned" label="Planned production time" value={450} unit="min" />
34
+ <Given label="Downtime" value={38} unit="min" />
35
+ </Difference>
36
+ <Ref to="planned" />
37
+ </Quotient>
38
+ <Quotient label="Performance" format="percent">
39
+ <Product label="Ideal run time" unit="min">
40
+ <Given label="Ideal cycle time" value={0.8} unit="min/pc" />
41
+ <Given id="total" label="Total count" value={480} unit="pcs" />
42
+ </Product>
43
+ <Ref to="runtime" />
44
+ </Quotient>
45
+ <Quotient label="Quality" format="percent">
46
+ <Given label="Good count" value={461} unit="pcs" />
47
+ <Ref to="total" />
48
+ </Quotient>
49
+ </Product>
50
+ </Calculation>
51
+ ```
52
+
53
+ A **chain** suits a sheet read top to bottom (ADR-0028). A first quantity,
54
+ then `Plus`, `Minus`, `Times` or `DividedBy`, each working its operand into the
55
+ value before it, strictly in order, and `Interim`s naming the value where they
56
+ stand. A chain stands open, as on paper - each line in view, a rule, the
57
+ interim. What should show only on request goes into one line as a tree, and
58
+ folds:
59
+
60
+ ```tsx
61
+ <Calculation aria-label="Payslip, March">
62
+ <Chain>
63
+ <Given id="gross" label="Gross salary" value={4200} unit="€" />
64
+ <Minus label="Income tax" value={612.5} unit="€" />
65
+ <Minus label="Church tax" value={49} unit="€" />
66
+ <Minus>
67
+ <Sum label="Social security contributions" unit="€">
68
+ <Given label="Pension insurance" value={390.6} unit="€" />
69
+ <Given label="Unemployment insurance" value={54.6} unit="€" />
70
+ <Given label="Health insurance" value={344.4} unit="€" />
71
+ <Given label="Long-term care insurance" value={71.4} unit="€" />
72
+ </Sum>
73
+ </Minus>
74
+ <Interim label="Net salary" unit="€" decimals={2} />
75
+ <Minus label="Capital-forming benefits" value={40} unit="€" />
76
+ <Plus label="Travel allowance" value={60} unit="€" />
77
+ <Interim label="Amount paid out" unit="€" decimals={2} />
78
+ </Chain>
79
+ </Calculation>
80
+ ```
81
+
82
+ The two mix: a line of a chain can hold a tree, and a chain can be an operand
83
+ in a tree - there it folds, and opens whole.
84
+
85
+ - **Four operators, and nothing else:** `Sum`, `Product`, `Difference`
86
+ (a − b − c) take two or more operands, `Quotient` exactly two.
87
+ - **A chain has no precedence**, so a `Times` or `DividedBy` stands alone
88
+ between two named values; anything else fails on the first render.
89
+ - **Full precision throughout**; rounding happens only on the screen. Where the
90
+ rounded operands do not give the rounded result, the result carries "≈".
91
+ - **An absent given is never zero.** Every quantity that depends on it is
92
+ absent too, with the reason; a quotient by zero is absent with its own.
93
+ - **Target and limits** are assessed through core's `assess()`. A folded
94
+ derivation that holds a worse verdict than its line says so, quietly.
95
+ - **A statement on a surface**, in the material of the library's tables and
96
+ cards. A derivation opens beneath its row as one group with it and closes
97
+ with "= label"; folded, the row shows the formula it hides in names — or how
98
+ many operands there are, above four. The Result is the last row, its number
99
+ underlined twice.
100
+ - **Every line is read as one sentence** — "Availability equals Run time
101
+ divided by Planned production time, equals 412 min divided by 450 min,
102
+ equals 91.6 percent" — and every derivation is a disclosure.
103
+ - **A declaration the calculation cannot evaluate fails on the first render**
104
+ with a message saying which and where: a `Ref` to nothing (listing the ids
105
+ that exist), a circle through references, a wrong operand count, a
106
+ duplicate id, a component of your own wrapping `Given`. `.map` inside an
107
+ operator works.
108
+
109
+ ## Documentation
110
+
111
+ The demo is the documentation: `pnpm dev:calculation` in the repository, port
112
+ 4177. The vocabulary — calculation, quantity, result, given, operator, operand,
113
+ derivation, reference — stands in `CONTEXT.md` under "Calculations".
@@ -0,0 +1 @@
1
+ @layer umriss.tokens,umriss.base,umriss.components;@layer umriss.base{._text_1hv7a_23{font-family:var(--u-font-sans);font-size:var(--u-text-md);line-height:var(--u-leading-normal);color:var(--u-color-text);-webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility}._field_1hv7a_34{caret-color:var(--u-color-accent)}._scroll_1hv7a_40{scrollbar-width:thin;scrollbar-color:color-mix(in srgb,var(--u-color-text) 25%,transparent) transparent}._scroll_1hv7a_40::-webkit-scrollbar{width:10px;height:10px}._scroll_1hv7a_40::-webkit-scrollbar-thumb{background:color-mix(in srgb,var(--u-color-text) 22%,transparent);border-radius:var(--u-radius-full);border:3px solid transparent;background-clip:content-box}._scroll_1hv7a_40::-webkit-scrollbar-track{background:transparent}}@layer umriss.components{._ring_1hv7a_65:focus-visible{outline:none;box-shadow:var(--u-focus-ring)}@media(pointer:coarse){._field_1hv7a_34._field_1hv7a_34{font-size:16px}}._field_1hv7a_34:-webkit-autofill,._field_1hv7a_34:-webkit-autofill:hover,._field_1hv7a_34:-webkit-autofill:focus{-webkit-box-shadow:inset 0 0 0 1000px var(--u-color-surface),0 0 0 1px var(--u-edge-color-strong);-webkit-text-fill-color:var(--u-color-text);caret-color:var(--u-color-accent)}}@layer umriss.tokens,umriss.base,umriss.components;@layer umriss.components{._frame_1hhx1_21,._frame_1hhx1_21:before,._frame_1hhx1_21:after{box-sizing:border-box}._calculation_1hhx1_39,._calculation_1hhx1_39:before,._calculation_1hhx1_39:after{box-sizing:border-box}._disclosure_1hhx1_53,._disclosure_1hhx1_53:before,._disclosure_1hhx1_53:after{box-sizing:border-box}._list_1hhx1_57,._list_1hhx1_57:before,._list_1hhx1_57:after{box-sizing:border-box}._item_1hhx1_58,._item_1hhx1_58:before,._item_1hhx1_58:after{box-sizing:border-box}._derivation_1hhx1_59,._derivation_1hhx1_59:before,._derivation_1hhx1_59:after{box-sizing:border-box}._line_1hhx1_109,._line_1hhx1_109:before,._line_1hhx1_109:after{box-sizing:border-box}._labelCell_1hhx1_151,._labelCell_1hhx1_151:before,._labelCell_1hhx1_151:after{box-sizing:border-box}._names_1hhx1_152,._names_1hhx1_152:before,._names_1hhx1_152:after{box-sizing:border-box}._operator_1hhx1_153,._operator_1hhx1_153:before,._operator_1hhx1_153:after{box-sizing:border-box}._amount_1hhx1_154,._amount_1hhx1_154:before,._amount_1hhx1_154:after{box-sizing:border-box}._unit_1hhx1_155,._unit_1hhx1_155:before,._unit_1hhx1_155:after{box-sizing:border-box}._assessment_1hhx1_156,._assessment_1hhx1_156:before,._assessment_1hhx1_156:after{box-sizing:border-box}._label_1hhx1_151,._label_1hhx1_151:before,._label_1hhx1_151:after{box-sizing:border-box}._approximate_1hhx1_269,._approximate_1hhx1_269:before,._approximate_1hhx1_269:after{box-sizing:border-box}._target_1hhx1_297,._target_1hhx1_297:before,._target_1hhx1_297:after{box-sizing:border-box}._worst_1hhx1_298,._worst_1hhx1_298:before,._worst_1hhx1_298:after{box-sizing:border-box}._notes_1hhx1_390,._notes_1hhx1_390:before,._notes_1hhx1_390:after{box-sizing:border-box}._freshness_1hhx1_404,._freshness_1hhx1_404:before,._freshness_1hhx1_404:after{box-sizing:border-box}._frame_1hhx1_21{font-size:var(--u-text-sm);border-radius:var(--u-radius-md);corner-shape:squircle;background:var(--u-color-surface);box-shadow:var(--u-edge);overflow:hidden;--disclosure-size: 20px;--disclosure-gap: var(--u-space-2);container-type:inline-size;width:100%}._calculation_1hhx1_39{display:grid;grid-template-columns:fit-content(22rem) minmax(0,1fr) auto auto auto auto;margin:0;padding:0;list-style:none}._frame_1hhx1_21[data-still]{--disclosure-size: 0px;--disclosure-gap: 0px}._frame_1hhx1_21[data-still] ._disclosure_1hhx1_53{display:none}._list_1hhx1_57,._item_1hhx1_58,._derivation_1hhx1_59{display:grid;grid-template-columns:subgrid;grid-column:1 / -1;margin:0;padding:0;list-style:none}._derivation_1hhx1_59[hidden]{display:none}._derivation_1hhx1_59:not([hidden]){animation:_derivationIn_1hhx1_1 var(--u-duration-fast) var(--u-ease-out)}@keyframes _derivationIn_1hhx1_1{0%{opacity:0}}._item_1hhx1_58{--fall: calc(var(--u-space-3) + var(--depth, 0) * var(--u-space-5) + var(--disclosure-size) / 2 - 1px)}._derivation_1hhx1_59{position:relative}._derivation_1hhx1_59:before{content:"";position:absolute;top:0;bottom:0;left:var(--fall);z-index:1;width:2px;background:var(--u-color-accent);pointer-events:none}:not(._derivation_1hhx1_59)>._item_1hhx1_58[data-open]>._line_1hhx1_109:not([data-mark]),:not(._derivation_1hhx1_59)>._item_1hhx1_58>._derivation_1hhx1_59{background-color:var(--u-color-surface-sunken)}._derivation_1hhx1_59 ._line_1hhx1_109{border-top-color:transparent}._line_1hhx1_109{position:relative;display:grid;grid-template-columns:subgrid;grid-column:1 / -1;align-items:baseline;border-top:1px solid var(--u-hairline)}._calculation_1hhx1_39>._item_1hhx1_58:first-child>._line_1hhx1_109:first-child,._calculation_1hhx1_39>._item_1hhx1_58:first-child>._list_1hhx1_57>._item_1hhx1_58:first-child>._line_1hhx1_109{border-top-color:transparent}._line_1hhx1_109[data-foldable]{cursor:pointer}._line_1hhx1_109[data-mark]{background:color-mix(in srgb,var(--u-color-accent-subtle) 50%,transparent)}._line_1hhx1_109[data-mark=use]{background:var(--u-color-accent-subtle)}._labelCell_1hhx1_151,._names_1hhx1_152,._operator_1hhx1_153,._amount_1hhx1_154,._unit_1hhx1_155,._assessment_1hhx1_156{grid-row:1;padding-top:var(--u-space-2);padding-bottom:var(--u-space-2)}._labelCell_1hhx1_151{grid-column:1;display:flex;align-items:baseline;gap:var(--disclosure-gap);min-width:0;padding-left:calc(var(--u-space-3) + var(--depth, 0) * var(--u-space-5));padding-right:var(--u-space-5)}._label_1hhx1_151{min-width:0;color:var(--u-color-text)}._line_1hhx1_109[data-reference] ._label_1hhx1_151{font-style:italic;color:var(--u-color-text-secondary)}._disclosure_1hhx1_53{flex:none;align-self:center;display:inline-flex;align-items:center;justify-content:center;width:var(--disclosure-size);height:var(--disclosure-size);padding:0;border:none;border-radius:var(--u-radius-xs);corner-shape:squircle;background:none;color:var(--u-color-text-muted)}button._disclosure_1hhx1_53{box-sizing:border-box}button._disclosure_1hhx1_53{cursor:pointer}button._disclosure_1hhx1_53:hover,._line_1hhx1_109[data-foldable]:hover button._disclosure_1hhx1_53{box-sizing:border-box}button._disclosure_1hhx1_53:hover,._line_1hhx1_109[data-foldable]:hover button._disclosure_1hhx1_53{background:var(--u-color-surface-sunken);color:var(--u-color-text)}._item_1hhx1_58[data-open]>._line_1hhx1_109 button._disclosure_1hhx1_53{box-sizing:border-box}._item_1hhx1_58[data-open]>._line_1hhx1_109 button._disclosure_1hhx1_53{color:var(--u-color-accent)}button._disclosure_1hhx1_53:focus-visible{box-sizing:border-box}button._disclosure_1hhx1_53:focus-visible{outline:none;box-shadow:var(--u-focus-ring)}._disclosure_1hhx1_53 svg{box-sizing:border-box}._disclosure_1hhx1_53 svg{transition:transform var(--u-duration-fast) var(--u-ease-out)}._disclosure_1hhx1_53 svg[data-open]{box-sizing:border-box}._disclosure_1hhx1_53 svg[data-open]{transform:rotate(90deg)}._names_1hhx1_152{grid-column:2;min-width:0;overflow:hidden;padding-right:var(--u-space-4);white-space:nowrap;text-overflow:ellipsis;color:var(--u-color-text-secondary)}._operator_1hhx1_153,._amount_1hhx1_154,._unit_1hhx1_155{font-family:var(--u-font-mono);font-variant-numeric:tabular-nums;font-feature-settings:"tnum" 1;white-space:nowrap}._operator_1hhx1_153{grid-column:3;padding-right:var(--u-space-3);text-align:right;color:var(--u-color-text-secondary)}._amount_1hhx1_154{grid-column:4;text-align:right;color:var(--u-color-text)}._amount_1hhx1_154[data-verdict=unknown]{color:var(--u-color-text-secondary)}._approximate_1hhx1_269{display:inline-block;margin-right:var(--u-space-1);color:var(--u-color-text-secondary);cursor:help}._unit_1hhx1_155{grid-column:5;padding-left:var(--u-space-1);padding-right:var(--u-space-3);color:var(--u-color-text-secondary)}._assessment_1hhx1_156{grid-column:6;display:flex;align-items:baseline;justify-content:flex-end;gap:var(--u-space-2);padding-right:var(--u-space-3);white-space:nowrap}._assessment_1hhx1_156:empty{padding-right:0}._target_1hhx1_297,._worst_1hhx1_298{font-size:var(--u-text-xs);color:var(--u-color-text-secondary)}._worst_1hhx1_298:before{content:"● "}._worst_1hhx1_298[data-verdict=unknown]:before{color:var(--u-color-text-muted)}._worst_1hhx1_298[data-verdict=warning]:before{color:var(--u-color-warning)}._worst_1hhx1_298[data-verdict=alarm]:before{color:var(--u-color-danger)}._line_1hhx1_109[data-inner]>._amount_1hhx1_154{color:var(--u-color-text-secondary)}._line_1hhx1_109[data-kind=interim] ._label_1hhx1_151,._line_1hhx1_109[data-kind=interim]>._amount_1hhx1_154{font-weight:var(--u-weight-medium)}._line_1hhx1_109[data-kind=interim]:before{content:"";grid-row:1;grid-column:3 / 6;align-self:start;border-top:1px solid var(--u-color-text-secondary);pointer-events:none}._line_1hhx1_109[data-closing] ._label_1hhx1_151{font-weight:var(--u-weight-medium);color:var(--u-color-text-secondary)}._line_1hhx1_109[data-closing]:before{content:"";grid-row:1;grid-column:3 / 6;align-self:start;border-top:1px solid var(--u-color-text-muted);pointer-events:none}._line_1hhx1_109[data-kind=result]{border-top:1px solid var(--u-edge-color-strong)}._line_1hhx1_109[data-kind=result]:not([data-mark]){background:var(--u-color-surface-sunken)}._line_1hhx1_109[data-kind=result] ._label_1hhx1_151{font-weight:var(--u-weight-semibold)}._line_1hhx1_109[data-kind=result]>._amount_1hhx1_154{font-size:var(--u-text-md);font-weight:var(--u-weight-semibold)}._line_1hhx1_109[data-kind=result]>._amount_1hhx1_154:not([data-verdict=unknown]){text-decoration-line:underline;text-decoration-style:double;text-underline-offset:5px}._notes_1hhx1_390{grid-row:2;grid-column:1 / 3;display:flex;flex-wrap:wrap;align-items:center;column-gap:var(--u-space-3);margin-top:calc(-1 * var(--u-space-1));padding:0 var(--u-space-4) var(--u-space-2) calc(var(--u-space-3) + var(--depth, 0) * var(--u-space-5) + var(--disclosure-size) + var(--disclosure-gap));font-size:var(--u-text-xs);color:var(--u-color-text-secondary)}._freshness_1hhx1_404[data-freshness=stale]{color:var(--u-color-warning)}._freshness_1hhx1_404[data-freshness=lost]{color:var(--u-color-danger);font-weight:var(--u-weight-medium)}@container (max-width: 34rem){._calculation_1hhx1_39{grid-template-columns:minmax(0,1fr) 0 auto auto auto 0}._names_1hhx1_152{display:none}._labelCell_1hhx1_151{padding-right:var(--u-space-3)}._assessment_1hhx1_156:not(:empty){grid-area:2 / 1 / 3 / -1;justify-content:flex-start;flex-wrap:wrap;padding:0 var(--u-space-3) var(--u-space-2) calc(var(--u-space-3) + var(--depth, 0) * var(--u-space-5) + var(--disclosure-size) + var(--disclosure-gap));white-space:normal}._notes_1hhx1_390{grid-row:3;grid-column:1 / -1}}@media(prefers-reduced-motion:reduce){._disclosure_1hhx1_53 svg{transition:none}._derivation_1hhx1_59:not([hidden]){animation:none}}}
@@ -0,0 +1,409 @@
1
+ import "./calculation.css";
2
+ import { jsx as f, jsxs as k } from "react/jsx-runtime";
3
+ import { Children as ve, isValidElement as j, Fragment as ge, useId as _e, useState as oe } from "react";
4
+ import { assess as be, verdictWeight as K, useFormats as me, useWording as fe, VisuallyHidden as $e, AngleGlyph as xe, Tooltip as re, Badge as ie, useFreshness as Ne } from "@umriss-ui/core";
5
+ function we(t) {
6
+ return t.decimals !== void 0 ? t.decimals : t.format === "percent" ? 1 : t.given ? void 0 : 2;
7
+ }
8
+ function ye(t, n) {
9
+ return n === void 0 ? t : Math.sign(t) * Number(Math.abs(t).toFixed(n));
10
+ }
11
+ function G(t, n) {
12
+ return ye(t.format === "percent" ? n * 100 : n, we(t));
13
+ }
14
+ const Oe = (t, n) => t.format === "percent" ? n / 100 : n;
15
+ function le(t, n, v) {
16
+ const [m, ...i] = n;
17
+ switch (t) {
18
+ case "sum":
19
+ return i.reduce((e, h, o) => v[o + 1] ? e - h : e + h, m);
20
+ case "difference":
21
+ return i.reduce((e, h) => e - h, m);
22
+ case "product":
23
+ return i.reduce((e, h) => e * h, m);
24
+ case "quotient":
25
+ return m / i[0];
26
+ }
27
+ }
28
+ const ce = (t, n) => n === void 0 || t !== void 0 && K(t) >= K(n) ? t : n;
29
+ function Se(t) {
30
+ const n = /* @__PURE__ */ new Map(), v = (m) => {
31
+ var $, y, T;
32
+ const i = n.get(m);
33
+ if (i) return i;
34
+ const e = t.quantities.get(m);
35
+ let h = null, o, w = !1, C;
36
+ if (e.given) {
37
+ const x = e.given.value;
38
+ typeof x == "number" && Number.isFinite(x) ? h = x : o = { kind: "missing", label: e.label };
39
+ } else {
40
+ const x = e.operands.map((a) => ({ operand: a, ...v(a.key) }));
41
+ for (const { operand: a, verdict: c, worst: r } of x) {
42
+ const _ = a.reference ? c : ce(c, r);
43
+ C = ce(C, _);
44
+ }
45
+ o = ($ = x.find((a) => a.absence)) == null ? void 0 : $.absence;
46
+ const O = x[1];
47
+ if (!o && e.operator === "quotient" && O.value === 0 && (o = { kind: "zero", label: t.quantities.get(O.operand.key).label }), !o) {
48
+ const a = e.operands.map((r) => r.negated === !0);
49
+ h = le(e.operator, x.map((r) => r.value), a);
50
+ const c = le(
51
+ e.operator,
52
+ x.map((r) => Oe(t.quantities.get(r.operand.key), r.shown)),
53
+ a
54
+ );
55
+ w = G(e, c) !== G(e, h);
56
+ }
57
+ }
58
+ const d = { target: e.target, limits: e.limits }, g = e.target !== void 0 || (((y = e.limits) == null ? void 0 : y.length) ?? 0) > 0 ? be(h, d) : void 0, b = (((T = e.limits) == null ? void 0 : T.length) ?? 0) > 0 ? g.verdict : h === null ? "unknown" : void 0, l = {
59
+ value: h,
60
+ absence: o,
61
+ shown: h === null ? null : G(e, h),
62
+ approximate: w,
63
+ assessment: g,
64
+ verdict: b,
65
+ worst: C
66
+ };
67
+ return n.set(m, l), l;
68
+ };
69
+ for (const m of t.quantities.keys()) v(m);
70
+ return n;
71
+ }
72
+ const qe = () => null, Te = () => null, ke = () => null, Ce = () => null, Ae = () => null, ue = () => null, Ie = () => null, Me = () => null, Pe = () => null, De = () => null, Ee = () => null, Y = () => null, Be = /* @__PURE__ */ new Map([
73
+ [Te, "sum"],
74
+ [ke, "difference"],
75
+ [Ce, "product"],
76
+ [Ae, "quotient"]
77
+ ]), We = {
78
+ sum: "Sum",
79
+ difference: "Difference",
80
+ product: "Product",
81
+ quotient: "Quotient"
82
+ }, Z = /* @__PURE__ */ new Map([
83
+ [Me, { name: "Plus", operator: "sum" }],
84
+ [Pe, { name: "Minus", operator: "sum", negated: !0 }],
85
+ [De, { name: "Times", operator: "product" }],
86
+ [Ee, { name: "DividedBy", operator: "quotient" }]
87
+ ]), X = "<Given>, <Sum>, <Difference>, <Product>, <Quotient> or <Chain>";
88
+ function q(t) {
89
+ throw new Error(`@umriss-ui/calculation: ${t}`);
90
+ }
91
+ function Q(t) {
92
+ return ve.toArray(t).flatMap(
93
+ (n) => j(n) && n.type === ge ? Q(n.props.children) : [n]
94
+ );
95
+ }
96
+ function V(t) {
97
+ if (!j(t)) return JSON.stringify(t);
98
+ const n = t.type;
99
+ return typeof n == "string" ? `<${n}>` : `<${n.displayName ?? n.name ?? "anonymous"}>`;
100
+ }
101
+ const ee = (t, n, v) => `${t}/${j(n) && n.key || v}`;
102
+ function je(t) {
103
+ const n = Q(t);
104
+ n.length !== 1 && q(`<Calculation> takes exactly one child, the result; it was given ${n.length}.`);
105
+ const v = /* @__PURE__ */ new Map(), m = /* @__PURE__ */ new Map(), i = [], e = (d, g, b) => {
106
+ const { id: l, ...$ } = g;
107
+ l !== void 0 && (m.has(l) && q(`${b}: the id "${l}" is used twice.`), m.set(l, d));
108
+ const y = { ...$, key: d, id: l, operands: [] };
109
+ return v.set(d, y), y;
110
+ }, h = (d, g, b) => {
111
+ if (j(d) && d.type === ue) {
112
+ const l = { key: "", reference: !0 };
113
+ return i.push({ operand: l, to: d.props.to, where: b }), l;
114
+ }
115
+ return { key: o(d, g, b), reference: !1 };
116
+ }, o = (d, g, b) => {
117
+ if ((!j(d) || d.type === ue || Z.has(d.type) || d.type === Y) && q(`${b}: ${V(d)} cannot stand here; a quantity must be ${X}.`), d.type === Ie) return w(d.props.children, g, b);
118
+ const l = Be.get(d.type);
119
+ d.type !== qe && l === void 0 && q(
120
+ `${b}: ${V(d)} is not an element of the calculation. A calculation reads its children's props and cannot look inside a component of your own; write ${X} or <Ref> directly, or return them from .map.`
121
+ );
122
+ const { children: $, value: y, source: T, asOf: x, ages: O, ...a } = d.props, c = `${b} › ${a.label}`, r = e(g, a, c);
123
+ if (l === void 0)
124
+ return r.given = { value: y, source: T, asOf: x, ages: O }, g;
125
+ r.operator = l;
126
+ const _ = Q($), s = _.length;
127
+ return (l === "quotient" ? s !== 2 : s < 2) && q(
128
+ `${c}: <${We[l]}> takes ${l === "quotient" ? "exactly two operands" : "two or more operands"}; it was given ${s}.`
129
+ ), r.operands = _.map((u, N) => h(u, ee(g, u, N), c)), g;
130
+ }, w = (d, g, b) => {
131
+ const l = `${b} › <Chain>`, [$, ...y] = Q(d);
132
+ ($ === void 0 || j($) && (Z.has($.type) || $.type === Y)) && q(`${l}: a chain starts with a quantity that has no operator - ${X} or <Ref>.`);
133
+ let T = h($, ee(g, $, 0), l), x = V($), O = [], a;
134
+ return y.forEach((c, r) => {
135
+ const _ = ee(g, c, r + 1);
136
+ if (j(c) && c.type === Y) {
137
+ const M = c.props, D = `${l} › ${M.label}`;
138
+ O.length === 0 && q(`${D}: an <Interim> needs an operand since the named value before it, ${x}.`);
139
+ const S = e(_, M, D);
140
+ S.interim = !0, S.operator = O[0].line.operator, S.operands = [T, ...O.map((J) => J.operand)], T = { key: _, reference: !1, previous: !0 }, x = `<Interim label="${M.label}">`, O = [], a = _;
141
+ return;
142
+ }
143
+ const s = j(c) ? Z.get(c.type) : void 0;
144
+ s === void 0 && q(`${l}: ${V(c)} needs an operator in a chain - <Plus>, <Minus>, <Times> or <DividedBy> - or is an <Interim>.`);
145
+ const u = s.operator !== "sum", N = O.find((M) => M.line.operator !== "sum");
146
+ (u && O.length > 0 || N) && q(
147
+ `${l}: <${(N ?? { line: s }).line.name}> stands alone between two named values - directly after the first quantity or an <Interim>, and directly before an <Interim>.`
148
+ );
149
+ const { children: B, ...A } = c.props, W = Q(B), I = () => q(`${l}: <${s.name}> takes either a label and a value, or exactly one quantity as its child.`);
150
+ if (W.length > 0) {
151
+ (W.length > 1 || Object.values(A).some((D) => D !== void 0)) && I();
152
+ const M = h(W[0], _, l);
153
+ M.negated = s.negated, O.push({ operand: M, line: s });
154
+ return;
155
+ }
156
+ A.label === void 0 && I();
157
+ const { value: E, source: U, asOf: F, ages: R, ...L } = A, z = e(_, { ...L, label: A.label }, `${l} › ${A.label}`);
158
+ z.given = { value: E, source: U, asOf: F, ages: R }, O.push({ operand: { key: _, reference: !1, negated: s.negated }, line: s });
159
+ }), (O.length > 0 || a === void 0) && q(`${l}: a chain ends with an <Interim>, which names its value.`), a;
160
+ }, C = o(n[0], "0", "<Calculation>");
161
+ for (const { operand: d, to: g, where: b } of i) {
162
+ const l = m.get(g);
163
+ if (l === void 0) {
164
+ const $ = [...m.keys()].map((y) => `"${y}"`).join(", ");
165
+ q(`${b}: <Ref to="${g}"> names no quantity. ${$ ? `The ids that exist: ${$}.` : "No quantity has an id."}`);
166
+ }
167
+ d.key = l;
168
+ }
169
+ return Re(v), { result: C, quantities: v };
170
+ }
171
+ function Re(t) {
172
+ const n = /* @__PURE__ */ new Set(), v = [], m = (i) => {
173
+ if (n.has(i)) return;
174
+ const e = v.indexOf(i);
175
+ if (e !== -1) {
176
+ const h = [...v.slice(e), i].map((o) => t.get(o).label);
177
+ q(`the calculation goes round in a circle through references: ${h.join(" → ")}.`);
178
+ }
179
+ v.push(i);
180
+ for (const h of t.get(i).operands) m(h.key);
181
+ v.pop(), n.add(i);
182
+ };
183
+ for (const i of t.keys()) m(i);
184
+ }
185
+ const de = 4, Fe = {
186
+ sum: "calculationSumSymbol",
187
+ difference: "calculationDifferenceSymbol",
188
+ product: "calculationProductSymbol",
189
+ quotient: "calculationQuotientSymbol"
190
+ }, Qe = {
191
+ sum: "calculationSumWord",
192
+ difference: "calculationDifferenceWord",
193
+ product: "calculationProductWord",
194
+ quotient: "calculationQuotientWord"
195
+ };
196
+ function H(t, n) {
197
+ switch (t) {
198
+ case "alarm":
199
+ return n.verdictAlarm;
200
+ case "warning":
201
+ return n.verdictWarning;
202
+ case "unknown":
203
+ return n.verdictUnknown;
204
+ default:
205
+ return n.verdictOk;
206
+ }
207
+ }
208
+ function te(t, n, v) {
209
+ const m = t.negated ? "difference" : t.operator;
210
+ return v[(n ? Qe : Fe)[m]];
211
+ }
212
+ function Ue(t, n) {
213
+ return t.kind === "missing" ? n.calculationMissing(t.label) : n.calculationDivisionByZero(t.label);
214
+ }
215
+ function pe(t, n, v, m) {
216
+ const i = t.format === "percent" ? m : t.unit, e = v.number(n, t.decimals);
217
+ return i ? `${e} ${i}` : e;
218
+ }
219
+ function Le(t, n, v, m, i, e, h) {
220
+ const o = t.quantities.get(v), w = n.get(v), C = (_, s, u) => s.shown === null ? u ? e.verdictUnknown : e.statAbsentValue : pe(_, s.shown, i, u ? e.calculationPercent : "%"), d = (_) => {
221
+ if (m || !o.operator) return {};
222
+ if (!_ && o.operands.length > de)
223
+ return { names: e.calculationOperandCount(o.operands.length) };
224
+ const s = (u) => o.operands.map((N, B) => {
225
+ const A = t.quantities.get(N.key);
226
+ return B === 0 ? u(A) : `${te({ operator: o.operator, negated: N.negated === !0 }, _, e)} ${u(A)}`;
227
+ }).join(" ");
228
+ return {
229
+ names: s((u) => u.label),
230
+ numbers: s((u) => C(u, n.get(u.key), _))
231
+ };
232
+ }, g = (_) => {
233
+ var N;
234
+ const s = (N = w.assessment) == null ? void 0 : N.deviation;
235
+ if (s === void 0 || o.target === void 0) return;
236
+ const u = pe(o, G(o, o.target), i, _ ? e.calculationPercent : "%");
237
+ return s > 0 ? e.calculationAboveTarget(u) : s < 0 ? e.calculationBelowTarget(u) : e.calculationOnTarget(u);
238
+ }, b = d(!1).names, l = b === void 0 || o.operands.length > de ? b : `= ${b}`, $ = d(!0), y = w.absence ? Ue(w.absence, e) : void 0, T = ` ${e.calculationEquals} `, x = w.approximate ? `${e.calculationApproximately} ${C(o, w, !0)}` : C(o, w, !0), a = [
239
+ (h ? `${te(h, !0, e)} ` : "") + o.label + T + [$.names, $.numbers, x].filter(Boolean).join(`,${T}`),
240
+ w.verdict !== void 0 && w.verdict !== "unknown" ? H(w.verdict, e) : void 0,
241
+ g(!0),
242
+ y
243
+ ].filter(Boolean).join(", "), c = w.shown === null ? void 0 : o.format === "percent" ? "%" : o.unit;
244
+ return { amount: w.shown === null ? e.statAbsentValue : i.number(w.shown, o.decimals), unit: c, names: l, target: g(!1), reason: y, sentence: a };
245
+ }
246
+ const Ve = "_frame_1hhx1_21 _text_1hv7a_23", Ge = "_calculation_1hhx1_39", He = "_disclosure_1hhx1_53", Ke = "_list_1hhx1_57", ze = "_item_1hhx1_58", Je = "_derivation_1hhx1_59", Ye = "_line_1hhx1_109", Ze = "_labelCell_1hhx1_151", Xe = "_names_1hhx1_152", et = "_operator_1hhx1_153", tt = "_amount_1hhx1_154", nt = "_unit_1hhx1_155", at = "_assessment_1hhx1_156", st = "_label_1hhx1_151", ot = "_approximate_1hhx1_269", rt = "_target_1hhx1_297", it = "_worst_1hhx1_298", lt = "_notes_1hhx1_390", ct = "_freshness_1hhx1_404", p = {
247
+ frame: Ve,
248
+ calculation: Ge,
249
+ disclosure: He,
250
+ list: Ke,
251
+ item: ze,
252
+ derivation: Je,
253
+ line: Ye,
254
+ labelCell: Ze,
255
+ names: Xe,
256
+ operator: et,
257
+ amount: tt,
258
+ unit: nt,
259
+ assessment: at,
260
+ label: st,
261
+ approximate: ot,
262
+ target: rt,
263
+ worst: it,
264
+ notes: lt,
265
+ freshness: ct
266
+ }, ut = { ok: "success", unknown: "neutral", warning: "warning", alarm: "danger" }, dt = (...t) => t.filter(Boolean).join(" ");
267
+ function pt({ asOf: t, ages: n }) {
268
+ const v = me(), m = fe(), i = Ne(t, n), e = i.freshness === "lost" ? m.freshnessDisconnected : i.freshness === "stale" ? m.freshnessStale : m.freshnessFresh;
269
+ return /* @__PURE__ */ k("span", { className: p.freshness, "data-freshness": i.freshness, children: [
270
+ e,
271
+ i.age !== null && ` · ${v.relative(i.age)}`
272
+ ] });
273
+ }
274
+ function vt({ children: t, className: n, style: v, ...m }) {
275
+ const i = me(), e = fe(), h = _e(), o = je(t), w = Se(o), [C, d] = oe(() => /* @__PURE__ */ new Set()), [g, b] = oe(null), l = (a) => d((c) => {
276
+ const r = new Set(c);
277
+ return r.delete(a) || r.add(a), r;
278
+ }), $ = (a, c, r) => r === 0 || c.previous ? void 0 : { operator: a.operator, negated: c.negated === !0 };
279
+ let y = !1;
280
+ const T = (a, c) => g === null ? void 0 : a === g ? "use" : c === g ? "operand" : void 0, x = (a, c, r, _) => {
281
+ const s = o.quantities.get(a), u = w.get(a), N = Le(o, w, a, c, i, e, r.position), B = s.operator !== void 0 && !c, A = a === o.result && !c, W = B && (s.interim ? r.flat : A), I = B && !W, E = I && C.has(a);
282
+ y || (y = I);
283
+ const U = `${h}-${a}`, F = { depth: W ? r.depth : r.depth + 1, flat: !1, parent: a }, R = B ? s.operands.find((P) => P.previous) : void 0, L = R && r.flat ? x(R.key, !1, { ...r, parent: a, position: void 0 }, `${_}-c`) : [], z = R && !r.flat ? x(R.key, !1, { ...F, flat: !0 }, `${_}-c`) : [], M = B ? s.operands.map(
284
+ (P, se) => P.previous ? null : x(P.key, P.reference, { ...F, position: $(s, P, se) }, `${_}-${se}`)
285
+ ) : [], D = I && !E && u.worst !== void 0 && K(u.worst) > K(u.verdict ?? "ok") ? u.worst : void 0, S = c ? void 0 : s.given, J = r.position ? te(r.position, !1, e) : void 0, ne = [
286
+ !c && s.explanation !== void 0 && /* @__PURE__ */ f("span", { children: s.explanation }, "explanation"),
287
+ (S == null ? void 0 : S.source) !== void 0 && /* @__PURE__ */ f("span", { children: e.calculationSource(S.source) }, "source"),
288
+ (S == null ? void 0 : S.asOf) !== void 0 && (S.ages !== void 0 ? /* @__PURE__ */ f(pt, { asOf: S.asOf, ages: S.ages }, "asOf") : /* @__PURE__ */ f("span", { children: e.asOfAgo(i.dateTime(new Date(S.asOf), !1)) }, "asOf")),
289
+ !c && s.aside !== void 0 && /* @__PURE__ */ f("span", { children: s.aside }, "aside")
290
+ ].filter(Boolean), he = A ? "result" : s.interim && !c && r.flat ? "interim" : void 0, ae = /* @__PURE__ */ k(
291
+ "div",
292
+ {
293
+ className: p.line,
294
+ style: { "--depth": r.depth },
295
+ "data-inner": r.depth > 0 ? "" : void 0,
296
+ "data-kind": he,
297
+ "data-foldable": I ? "" : void 0,
298
+ "data-reference": c ? "" : void 0,
299
+ "data-mark": T(a, r.parent),
300
+ onPointerEnter: () => b(a),
301
+ onPointerLeave: () => b(null),
302
+ onFocus: () => b(a),
303
+ onBlur: () => b(null),
304
+ onClick: I ? () => {
305
+ var P;
306
+ (P = window.getSelection()) != null && P.toString() || l(a);
307
+ } : void 0,
308
+ children: [
309
+ /* @__PURE__ */ f($e, { children: [
310
+ N.sentence,
311
+ u.approximate && e.calculationApproximateNote,
312
+ D !== void 0 && e.calculationWorstInside(H(D, e))
313
+ ].filter(Boolean).join(". ") }),
314
+ /* @__PURE__ */ k("span", { className: p.labelCell, children: [
315
+ I ? /* @__PURE__ */ f(
316
+ "button",
317
+ {
318
+ type: "button",
319
+ className: p.disclosure,
320
+ "aria-expanded": E,
321
+ "aria-controls": U,
322
+ "aria-label": (E ? e.calculationHideDerivation : e.calculationShowDerivation)(s.label),
323
+ children: /* @__PURE__ */ f(xe, { "data-open": E ? "" : void 0 })
324
+ }
325
+ ) : /* @__PURE__ */ f("span", { className: p.disclosure, "aria-hidden": "true" }),
326
+ /* @__PURE__ */ f("span", { className: p.label, "aria-hidden": "true", children: s.label })
327
+ ] }),
328
+ /* @__PURE__ */ f("span", { className: p.names, "aria-hidden": "true", children: I && !E ? N.names : void 0 }),
329
+ /* @__PURE__ */ f("span", { className: p.operator, "aria-hidden": "true", children: J }),
330
+ /* @__PURE__ */ k("span", { className: p.amount, "aria-hidden": "true", "data-verdict": u.verdict, children: [
331
+ u.approximate && /* @__PURE__ */ f(re, { content: e.calculationApproximateNote, children: /* @__PURE__ */ f("span", { className: p.approximate, children: "≈" }) }),
332
+ N.amount
333
+ ] }),
334
+ /* @__PURE__ */ f("span", { className: p.unit, "aria-hidden": "true", children: N.unit }),
335
+ /* @__PURE__ */ k("span", { className: p.assessment, "aria-hidden": "true", children: [
336
+ N.reason !== void 0 ? /* @__PURE__ */ f(ie, { children: N.reason }) : u.verdict !== void 0 && /* @__PURE__ */ f(ie, { tone: ut[u.verdict], children: H(u.verdict, e) }),
337
+ N.target !== void 0 && /* @__PURE__ */ f("span", { className: p.target, children: N.target }),
338
+ D !== void 0 && /* @__PURE__ */ f("span", { className: p.worst, "data-verdict": D, children: e.calculationWorstInside(H(D, e)) })
339
+ ] }),
340
+ ne.length > 0 && /* A click here - on a link in the aside, on a sparkline - is the
341
+ caller's, not a fold. */
342
+ /* @__PURE__ */ f("span", { className: p.notes, onClick: (P) => P.stopPropagation(), children: ne })
343
+ ]
344
+ }
345
+ );
346
+ return W ? [
347
+ ...L,
348
+ /* @__PURE__ */ k("li", { className: p.item, children: [
349
+ /* @__PURE__ */ f("ul", { className: p.list, children: M }),
350
+ ae
351
+ ] }, _)
352
+ ] : [
353
+ ...L,
354
+ /* @__PURE__ */ k("li", { className: p.item, "data-open": E ? "" : void 0, style: { "--depth": r.depth }, children: [
355
+ ae,
356
+ I && /* @__PURE__ */ k("ul", { id: U, className: p.derivation, hidden: !E, children: [
357
+ z,
358
+ M,
359
+ /* @__PURE__ */ f("li", { className: p.item, "aria-hidden": "true", children: /* @__PURE__ */ k(
360
+ "div",
361
+ {
362
+ className: p.line,
363
+ style: { "--depth": F.depth },
364
+ "data-inner": "",
365
+ "data-closing": "",
366
+ "data-mark": g === a ? "use" : void 0,
367
+ children: [
368
+ /* @__PURE__ */ k("span", { className: p.labelCell, children: [
369
+ /* @__PURE__ */ f("span", { className: p.disclosure }),
370
+ /* @__PURE__ */ k("span", { className: p.label, children: [
371
+ "= ",
372
+ s.label
373
+ ] })
374
+ ] }),
375
+ /* @__PURE__ */ f("span", { className: p.names }),
376
+ /* @__PURE__ */ f("span", { className: p.operator }),
377
+ /* @__PURE__ */ k("span", { className: p.amount, "data-verdict": u.verdict, children: [
378
+ u.approximate && /* @__PURE__ */ f(re, { content: e.calculationApproximateNote, children: /* @__PURE__ */ f("span", { className: p.approximate, children: "≈" }) }),
379
+ N.amount
380
+ ] }),
381
+ /* @__PURE__ */ f("span", { className: p.unit, children: N.unit })
382
+ ]
383
+ }
384
+ ) })
385
+ ] })
386
+ ] }, _)
387
+ ];
388
+ }, O = x(o.result, !1, { depth: 0, flat: !0, parent: null }, "r");
389
+ return (
390
+ /* The frame is the surface and the container the layout measures itself
391
+ against; the list inside it is the statement. */
392
+ /* @__PURE__ */ f("div", { className: dt(p.frame, n), style: v, "data-still": y ? void 0 : "", children: /* @__PURE__ */ f("ul", { className: p.calculation, ...m, children: O }) })
393
+ );
394
+ }
395
+ export {
396
+ vt as Calculation,
397
+ Ie as Chain,
398
+ ke as Difference,
399
+ Ee as DividedBy,
400
+ qe as Given,
401
+ Y as Interim,
402
+ Pe as Minus,
403
+ Me as Plus,
404
+ Ce as Product,
405
+ Ae as Quotient,
406
+ ue as Ref,
407
+ Te as Sum,
408
+ De as Times
409
+ };
@@ -0,0 +1,132 @@
1
+ import { FreshnessAges } from '@umriss-ui/core';
2
+ import { HTMLAttributes } from 'react';
3
+ import { JSX } from 'react';
4
+ import { Limit } from '@umriss-ui/core';
5
+ import { ReactNode } from 'react';
6
+
7
+ export declare function Calculation({ children, className, style, ...rest }: CalculationProps): JSX.Element;
8
+
9
+ /** `className` and `style` size and place the surface; every other attribute -
10
+ `aria-label` first of all - lands on the list the surface holds. */
11
+ export declare interface CalculationProps extends Omit<HTMLAttributes<HTMLUListElement>, "children"> {
12
+ /** The result: exactly one quantity - a tree (`<Sum>`, `<Difference>`,
13
+ `<Product>`, `<Quotient>` with their operands, `<Given>` as leaves) or a
14
+ `<Chain>`, the two mixing freely; `<Ref>` for a quantity defined
15
+ elsewhere. */
16
+ children: ReactNode;
17
+ }
18
+
19
+ /** A calculation read top to bottom, as on paper. */
20
+ export declare const Chain: (props: ChainProps) => null;
21
+
22
+ export declare interface ChainOperandProps extends Omit<GivenProps, "label" | "value"> {
23
+ /** The operand's name, where the line is a given written in place. Left
24
+ out where the line holds a quantity as its child. */
25
+ label?: string;
26
+ /** The operand's number, where the line is a given written in place.
27
+ Absent as for a `Given`: the line and every interim after it are
28
+ absent, with the reason. */
29
+ value?: number | null;
30
+ /** Instead of `label` and `value`: exactly one quantity - a tree, a
31
+ `<Ref>` or a `<Chain>`. */
32
+ children?: ReactNode;
33
+ }
34
+
35
+ export declare interface ChainProps {
36
+ /** The operands, top to bottom: a first quantity with no operator, then
37
+ `Plus`, `Minus`, `Times` and `DividedBy` with their operands and
38
+ `Interim`s naming the value where they stand. The last line is an
39
+ `Interim`; a `Times` or `DividedBy` stands alone between two named
40
+ values. */
41
+ children?: ReactNode;
42
+ }
43
+
44
+ /** The first operand minus every other: a − b − c. */
45
+ export declare const Difference: (props: OperatorProps) => null;
46
+
47
+ /** Divides the named value before it by its operand. */
48
+ export declare const DividedBy: (props: ChainOperandProps) => null;
49
+
50
+ /** A number the calculation does not derive. */
51
+ export declare const Given: (props: GivenProps) => null;
52
+
53
+ export declare interface GivenProps extends QuantityProps {
54
+ /** The number the calculation starts from. Absent (`null`, `undefined`,
55
+ not finite) makes every quantity that depends on it absent, with the
56
+ reason - never zero. */
57
+ value: number | null | undefined;
58
+ /** Where the number came from: a system, a report, a person. */
59
+ source?: string;
60
+ /** When the number was true - not when it was fetched. */
61
+ asOf?: Date | number;
62
+ /** The ages at which `asOf` turns stale and lost. With both, the given
63
+ carries a freshness through core; with `asOf` alone, only the time. */
64
+ ages?: FreshnessAges;
65
+ }
66
+
67
+ /** Names the value of a chain where it stands. */
68
+ export declare const Interim: (props: QuantityProps) => null;
69
+
70
+ /** Takes its operand from the value before it. */
71
+ export declare const Minus: (props: ChainOperandProps) => null;
72
+
73
+ export declare interface OperatorProps extends QuantityProps {
74
+ /** The operands, in order: quantities, givens and references. */
75
+ children?: ReactNode;
76
+ }
77
+
78
+ /** Adds its operand to the value before it. */
79
+ export declare const Plus: (props: ChainOperandProps) => null;
80
+
81
+ /** The product of two or more operands. */
82
+ export declare const Product: (props: OperatorProps) => null;
83
+
84
+ /** What every quantity - given or derived - can say about itself. */
85
+ export declare interface QuantityProps {
86
+ /** What the quantity is called. It stands on its line, wherever it is an
87
+ operand, and in the accessible sentence. */
88
+ label: string;
89
+ /** Makes the quantity referable: `<Ref to>` stands for it elsewhere. Needed
90
+ only for a quantity used more than once. */
91
+ id?: string;
92
+ /** A label written after the number - "min", "pcs", "€". Never converted
93
+ and never checked; the calculation does no unit algebra. */
94
+ unit?: string;
95
+ /** `percent` presents a plain ratio as per cent: 0.916 is shown as 91.6 %.
96
+ The quantity stays a ratio in every operation. */
97
+ format?: "percent";
98
+ /** Fixed number of fraction digits as shown. Rounding happens only in
99
+ presentation; every operation uses the full value. Without it: one digit
100
+ for a percentage, at most two for a derived number, and a given as it
101
+ was given. */
102
+ decimals?: number;
103
+ /** The value this quantity is meant to reach - missed, never violated. */
104
+ target?: number;
105
+ /** Bounds the quantity is assessed against, through core's `assess()`. */
106
+ limits?: readonly Limit[];
107
+ /** A sentence of plain text beside the line: why the quantity is here, or
108
+ what it means. */
109
+ explanation?: string;
110
+ /** Content of the caller's own beside the line - a sparkline, a link. It
111
+ never takes part in the calculation. */
112
+ aside?: ReactNode;
113
+ }
114
+
115
+ /** Exactly two operands: the first divided by the second. */
116
+ export declare const Quotient: (props: OperatorProps) => null;
117
+
118
+ /** A quantity defined elsewhere in the calculation, standing as an operand. */
119
+ export declare const Ref: (props: RefProps) => null;
120
+
121
+ export declare interface RefProps {
122
+ /** The `id` of the quantity this reference stands for. */
123
+ to: string;
124
+ }
125
+
126
+ /** The sum of two or more operands. */
127
+ export declare const Sum: (props: OperatorProps) => null;
128
+
129
+ /** Multiplies the named value before it by its operand. */
130
+ export declare const Times: (props: ChainOperandProps) => null;
131
+
132
+ export { }
package/package.json ADDED
@@ -0,0 +1,77 @@
1
+ {
2
+ "name": "@umriss-ui/calculation",
3
+ "version": "0.2.0",
4
+ "description": "The umriss calculation - a derivation written as it is shown, evaluated by the library, folded and read line by line.",
5
+ "license": "MIT",
6
+ "author": "Roman Händler",
7
+ "homepage": "https://romanhaendler.github.io/umriss-ui/calculation/",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/romanhaendler/umriss-ui.git",
11
+ "directory": "packages/calculation"
12
+ },
13
+ "bugs": {
14
+ "url": "https://github.com/romanhaendler/umriss-ui/issues"
15
+ },
16
+ "keywords": [
17
+ "react",
18
+ "components",
19
+ "ui",
20
+ "umriss",
21
+ "calculation",
22
+ "derivation"
23
+ ],
24
+ "type": "module",
25
+ "files": [
26
+ "dist",
27
+ "CHANGELOG.md"
28
+ ],
29
+ "publishConfig": {
30
+ "access": "public"
31
+ },
32
+ "sideEffects": [
33
+ "**/*.css"
34
+ ],
35
+ "main": "./dist/calculation.js",
36
+ "types": "./dist/index.d.ts",
37
+ "exports": {
38
+ ".": {
39
+ "types": "./dist/index.d.ts",
40
+ "import": "./dist/calculation.js"
41
+ },
42
+ "./styles.css": "./dist/calculation.css"
43
+ },
44
+ "peerDependencies": {
45
+ "react": ">=18",
46
+ "react-dom": ">=18",
47
+ "@umriss-ui/core": "^0.5.0"
48
+ },
49
+ "devDependencies": {
50
+ "@fontsource/geist-mono": "^5.3.0",
51
+ "@fontsource/geist-sans": "^5.3.0",
52
+ "@types/node": "^22.20.2",
53
+ "@types/react": "^19.0.0",
54
+ "@types/react-dom": "^19.0.0",
55
+ "@vitejs/plugin-react": "^4.3.4",
56
+ "react": "^19.0.0",
57
+ "react-dom": "^19.0.0",
58
+ "typescript": "^5.6.3",
59
+ "vite": "^6.0.0",
60
+ "vite-plugin-dts": "^4.3.0",
61
+ "@umriss-ui/core": "0.5.0",
62
+ "@umriss-ui/demo": "0.0.0"
63
+ },
64
+ "scripts": {
65
+ "props": "node --disable-warning=ExperimentalWarning --experimental-strip-types demo/props.ts",
66
+ "predev": "pnpm run props",
67
+ "dev": "vite --config vite.demo.config.ts",
68
+ "build": "vite build",
69
+ "prebuild:demo": "pnpm run props",
70
+ "build:demo": "vite build --config vite.demo.config.ts",
71
+ "pretypecheck": "pnpm run props",
72
+ "typecheck": "tsc --noEmit",
73
+ "preview:demo": "vite preview --config vite.demo.config.ts --port 4177 --strictPort",
74
+ "test:unit": "vitest run",
75
+ "check:dist": "node --disable-warning=ExperimentalWarning --experimental-strip-types ../../scripts/check-dist.ts calculation.js calculation.css"
76
+ }
77
+ }