@uniflowed/ui 0.0.0-alpha.14 → 0.0.0-alpha.16

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/combobox.js CHANGED
@@ -618,10 +618,10 @@ export component ComboboxOption(
618
618
  * a boundary they cannot see, and the heading is never a place the cursor can
619
619
  * land, because it is not an option.
620
620
  *
621
- * `children` is narrower than `Select.Group`'s `React.Node`, and the narrower
622
- * one is the true statement: a `group` inside a `listbox` may own options and
623
- * its own heading, and nothing else. `Select.Group` should say the same and
624
- * does not yet.
621
+ * `children` is the true statement rather than a `React.Node` that would take
622
+ * anything: a `group` inside a `listbox` may own options and its own heading,
623
+ * and nothing else. `Select.Group` says the same since ubugeeei-prod/uf#562 —
624
+ * it is the same listbox, and it took a second breaking change to get there.
625
625
  */
626
626
  export component ComboboxGroup(
627
627
  children: renders* (ComboboxOption | ComboboxGroupLabel),
package/field.js CHANGED
@@ -108,6 +108,22 @@ import { withProps } from "./internal/merge-props.js";
108
108
  * `onBlur` and the constraint attributes a progressive form emits — spread onto
109
109
  * whatever element `Field.Control` renders, underneath the attributes the field
110
110
  * computes.
111
+ *
112
+ * # It is declared here and produced there
113
+ *
114
+ * `@uniflowed/form`'s `useFieldSource` builds one of these, so this type is the
115
+ * seam between the two packages — and it lives on this side only because the
116
+ * dependency can only run this way today: this package is on npm and that one
117
+ * is not, and `tools/release/publishable.sh` refuses a published package that
118
+ * depends on an unpublished one.
119
+ *
120
+ * That is backwards, and ubugeeei-prod/uf#614 says so. It stands because
121
+ * declaring the type twice trades a documented edge for silent drift — a
122
+ * `Field.Root` accepting a shape `useFieldSource` no longer produces would
123
+ * type-check on both sides and fail only where they meet — and because the
124
+ * import is type-only, so no project installing `@uniflowed/form` loads,
125
+ * bundles or runs any of this package. #210 is the trigger: once
126
+ * `@uniflowed/form` publishes, this moves there and `@uniflowed/ui` imports it.
111
127
  */
112
128
  export type FieldSource = {|
113
129
  readonly invalid: boolean,
package/index.js CHANGED
@@ -39,6 +39,52 @@
39
39
  // the same components with exactly the same behaviour; the design-system layer
40
40
  // that adds uf's default styles is built *on* these, not into them.
41
41
  //
42
+ // # What is not here, and where it went instead
43
+ //
44
+ // About twenty of the catalogue this package is measured against have no
45
+ // behaviour at all. Badge, Card, Button, Input, Textarea, Label and Aspect
46
+ // Ratio are, between them, a class list and a `<div>`. For a library whose
47
+ // product *is* the styles that is coherent — you copy them in and you own
48
+ // them. It is not coherent here: a `Badge` with no styles is a `<span>`, a
49
+ // `Card` with no styles is a `<div>`, and shipping them from a package that
50
+ // ships no styles would make this a library of empty elements. Shipping them
51
+ // from `@uniflowed/stylex` would make *that* a component library, which its
52
+ // own header forbids. So for a long time the answer on record was both and
53
+ // neither, which is ubugeeei-prod/uf#298.
54
+ //
55
+ // The line that holds is not "styled versus headless". It is **whether the
56
+ // thing has a decision in it**:
57
+ //
58
+ // - An ARIA decision, a state machine or a keyboard requirement makes it a
59
+ // component here, even when it renders a single element. `Progress` is one
60
+ // `<div>`, and it belongs, because the conditional that omits
61
+ // `aria-valuenow` when the amount is unknown — rather than sending
62
+ // `aria-valuenow={0}`, which says "nothing has happened" — is the whole
63
+ // component. `Toggle` and `Checkbox` are the same shape for the same reason.
64
+ // - Anything that is only a class list belongs in `@uniflowed/stylex/preset`,
65
+ // which already has the right form: `buttonStyles({ tone, size })`,
66
+ // `cardStyles()`, `textStyles({ size, tone })` and `fieldStyles()` return
67
+ // `{ className }` for a caller to spread onto their own element. That is a
68
+ // better answer than a `<Badge>`, not a lesser one — a component wrapping a
69
+ // `<button>` takes away `type="submit"`, `formAction`, the ref and every
70
+ // attribute nobody thought to forward, and gives back a class name the
71
+ // caller could have written.
72
+ //
73
+ // This is stated rather than left as an omission, because an omission reads as
74
+ // an oversight and the next contributor closes it with a `<Badge>`.
75
+ // `crates/uf_lib/src/ui.rs` carries the same decision as data: those entries
76
+ // are `Declined` with the preset functions that replace them named on each,
77
+ // and `cargo test -p uf_lib` fails if a name there stops existing.
78
+ //
79
+ // Five of that twenty are not presentational and are missing rather than
80
+ // declined — Alert, Avatar, Breadcrumb, Separator and Skeleton — each for one
81
+ // specific reason, and the registry entry for each says which. The shortest is
82
+ // Alert: `role="alert"` is a live region, an element already in the document
83
+ // when the page loads announces on insertion or not at all, and a permanently
84
+ // rendered "your trial ends soon" box carrying that role is either an
85
+ // interruption on every page load or silence. `field.js` already makes that
86
+ // call correctly for `Field.Error`.
87
+ //
42
88
  // # What these components promise React
43
89
  //
44
90
  // Nothing here mutates during a render, reads a ref during a render, or depends
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uniflowed/ui",
3
- "version": "0.0.0-alpha.14",
3
+ "version": "0.0.0-alpha.16",
4
4
  "description": "Headless, accessible React components whose composition Flow checks, part of the Unified Toolchain for Flow.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -52,9 +52,9 @@
52
52
  "internal"
53
53
  ],
54
54
  "dependencies": {
55
- "@uniflowed/core": "0.0.0-alpha.14",
56
- "@uniflowed/hooks": "0.0.0-alpha.14",
57
- "@uniflowed/react": "0.0.0-alpha.14"
55
+ "@uniflowed/core": "0.0.0-alpha.16",
56
+ "@uniflowed/hooks": "0.0.0-alpha.16",
57
+ "@uniflowed/react": "0.0.0-alpha.16"
58
58
  },
59
59
  "peerDependencies": {
60
60
  "react": ">=19"
package/select.js CHANGED
@@ -822,8 +822,26 @@ export component SelectOption(
822
822
  * `Select.GroupLabel` is rendered — the same rule, and the same reason, as
823
823
  * `Menu.Group`. The arrow keys pass over the label without stopping on it,
824
824
  * because they only ever look for `role="option"`.
825
+ *
826
+ * `children` is `renders* (SelectOption | SelectGroupLabel)`, which is what a
827
+ * `group` inside a `listbox` may hold: options, and the heading that names
828
+ * them. It took `React.Node` until ubugeeei-prod/uf#562, so a `<div>` in a
829
+ * group was a runtime surprise — an element with no role between two options,
830
+ * which the arrow keys walk straight past and a screen reader reads as a stray
831
+ * line — rather than a type error. `Combobox.Group` has stated the constraint
832
+ * since #558 and this is the same listbox.
833
+ *
834
+ * No `Select.Separator`, and that is deliberate rather than an omission: a rule
835
+ * separates *groups*, so it belongs between them in `Select.List` — which does
836
+ * admit one. A separator inside a group is a rule with nothing on one side of
837
+ * it.
838
+ *
839
+ * **Breaking.** A caller passing anything else — a `<div>` wrapper, a fragment
840
+ * of their own, a component that returns options — now fails `uf check`. The
841
+ * fix is to hand the options to the group directly; a wrapper had no effect on
842
+ * what this renders, because the group's element is the one below.
825
843
  */
826
- export component SelectGroup(children: React.Node, ...rest: Rest) {
844
+ export component SelectGroup(children: renders* (SelectOption | SelectGroupLabel), ...rest: Rest) {
827
845
  const base = useId();
828
846
  const [labelled, setLabelled] = useState(false);
829
847