@sigx/zero-daisyui 0.11.0 → 0.13.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/README.md +5 -0
- package/dist/css/components/combobox.css +2 -1
- package/dist/css/components/radio-group.css +6 -0
- package/dist/css/index.css +8 -1
- package/dist/lynx/components/checkbox.css +44 -11
- package/dist/lynx/components/combobox.css +2 -1
- package/dist/lynx/components/fieldset.css +10 -1
- package/dist/lynx/components/input.css +40 -2
- package/dist/lynx/components/number-input.css +38 -3
- package/dist/lynx/components/radio-group.css +10 -3
- package/dist/lynx/components/textarea.css +31 -7
- package/dist/lynx/components/toast.css +0 -8
- package/dist/lynx/components/toggle-group.css +54 -2
- package/dist/lynx/components/toggle.css +27 -2
- package/dist/lynx/index.css +256 -46
- package/dist/lynx/manifest.json +3 -3
- package/dist/lynx/tokens.css +0 -6
- package/dist/manifest.json +1 -1
- package/dist/recipes.d.ts.map +1 -1
- package/dist/recipes.js +435 -23
- package/dist/recipes.js.map +1 -1
- package/dist/report.json +73 -13
- package/package.json +4 -4
- package/src/recipes.ts +440 -23
package/src/recipes.ts
CHANGED
|
@@ -1690,6 +1690,10 @@ export const field: RecipeInput = {
|
|
|
1690
1690
|
label: {
|
|
1691
1691
|
base: { fontSize: 'var(--text-sm)', fontWeight: 'var(--weight-semibold)', color: 'var(--field-accent)' },
|
|
1692
1692
|
states: { disabled: { opacity: 'var(--disabled-opacity)' } },
|
|
1693
|
+
// lynx has no pseudo-elements and the anatomy no part to carry
|
|
1694
|
+
// the asterisk, so it stays a web-only mark: on lynx a required
|
|
1695
|
+
// field still stamps `required` on its root and label, and the
|
|
1696
|
+
// control inside adopts it (signalxjs/lynx#1205).
|
|
1693
1697
|
selectors: {
|
|
1694
1698
|
'&[data-required]::after': { content: '" *"', color: roleInk('error') },
|
|
1695
1699
|
},
|
|
@@ -1781,6 +1785,33 @@ export const fieldset: RecipeInput = {
|
|
|
1781
1785
|
xl: { legend: { base: { fontSize: 'var(--text-lg)' } } },
|
|
1782
1786
|
},
|
|
1783
1787
|
},
|
|
1788
|
+
// Lynx has no grid: the one-column track becomes a flex column (the gap
|
|
1789
|
+
// survives), and the block/inline paddings are restated physically —
|
|
1790
|
+
// logical spellings resolve on iOS but not on Android (signalxjs/lynx#1084).
|
|
1791
|
+
targets: {
|
|
1792
|
+
lynx: {
|
|
1793
|
+
parts: {
|
|
1794
|
+
root: {
|
|
1795
|
+
base: {
|
|
1796
|
+
display: 'flex',
|
|
1797
|
+
flexDirection: 'column',
|
|
1798
|
+
paddingTop: 'var(--space-xs)',
|
|
1799
|
+
paddingBottom: 'var(--space-xs)',
|
|
1800
|
+
paddingLeft: '0',
|
|
1801
|
+
paddingRight: '0',
|
|
1802
|
+
},
|
|
1803
|
+
},
|
|
1804
|
+
legend: {
|
|
1805
|
+
base: {
|
|
1806
|
+
paddingTop: 'var(--space-sm)',
|
|
1807
|
+
paddingBottom: 'var(--space-sm)',
|
|
1808
|
+
paddingLeft: '0',
|
|
1809
|
+
paddingRight: '0',
|
|
1810
|
+
},
|
|
1811
|
+
},
|
|
1812
|
+
},
|
|
1813
|
+
},
|
|
1814
|
+
},
|
|
1784
1815
|
};
|
|
1785
1816
|
|
|
1786
1817
|
/**
|
|
@@ -1794,6 +1825,13 @@ export const fieldset: RecipeInput = {
|
|
|
1794
1825
|
* horizontal bar and lifts it to the middle. All three verbatim from daisyUI
|
|
1795
1826
|
* 5.7.8's `.checkbox:before` / `:checked` / `:indeterminate`.
|
|
1796
1827
|
*/
|
|
1828
|
+
/**
|
|
1829
|
+
* The lynx tick's stroke: 20% of the checkbox's content box — the width of
|
|
1830
|
+
* daisy's polygon bars — as a length, since borders take no percentage.
|
|
1831
|
+
* The content box is the control less its padding and border on both sides.
|
|
1832
|
+
*/
|
|
1833
|
+
const CHECKBOX_LYNX_STROKE = 'calc((var(--checkbox-size) - var(--checkbox-pad) * 2 - var(--border) * 2) * 0.2)';
|
|
1834
|
+
|
|
1797
1835
|
const TICK_COLLAPSED = 'polygon(20% 100%, 20% 80%, 50% 80%, 50% 80%, 70% 80%, 70% 100%)';
|
|
1798
1836
|
const TICK_DRAWN = 'polygon(20% 100%, 20% 80%, 50% 80%, 50% 0%, 70% 0%, 70% 100%)';
|
|
1799
1837
|
const DASH_DRAWN = 'polygon(20% 100%, 20% 80%, 50% 80%, 50% 80%, 80% 80%, 80% 100%)';
|
|
@@ -2012,33 +2050,83 @@ export const checkbox: RecipeInput = {
|
|
|
2012
2050
|
targets: {
|
|
2013
2051
|
lynx: {
|
|
2014
2052
|
parts: {
|
|
2053
|
+
// The box/label row, pinned: `inline-flex` is rewritten to
|
|
2054
|
+
// `flex`, and the row must not depend on the engine's
|
|
2055
|
+
// default direction (signalxjs/lynx#1203).
|
|
2056
|
+
root: { base: { display: 'flex', flexDirection: 'row' } },
|
|
2015
2057
|
control: {
|
|
2016
2058
|
// The web fallback (`color-mix(in oklab, … currentColor …)`)
|
|
2017
2059
|
// exists for consumers that unset the accent; this package
|
|
2018
2060
|
// always defines it, so the plain accent IS the ring.
|
|
2019
|
-
|
|
2061
|
+
//
|
|
2062
|
+
// `inline-block` is not a lynx display value (no inline
|
|
2063
|
+
// formatting context); a flex box keeps daisy's geometry —
|
|
2064
|
+
// the padding insets the 100%-sized indicator either way.
|
|
2065
|
+
base: { display: 'flex', border: 'var(--border) solid var(--checkbox-accent)' },
|
|
2066
|
+
// The ring as spread shadows that follow the box's radius
|
|
2067
|
+
// (signalxjs/lynx#1163 — lynx's `outline` ignores it and
|
|
2068
|
+
// has no offset), keeping the box's inset depth line.
|
|
2069
|
+
states: {
|
|
2070
|
+
'focus-visible': lynxFocusRing('var(--checkbox-accent)', { under: '0 1px var(--depth-shade) inset' }),
|
|
2071
|
+
},
|
|
2020
2072
|
},
|
|
2021
2073
|
indicator: {
|
|
2022
|
-
// The tick
|
|
2023
|
-
//
|
|
2024
|
-
//
|
|
2074
|
+
// The tick WITHOUT clip-path, which lynx does not apply
|
|
2075
|
+
// (measured on iOS, signalxjs/lynx#1216 — daisy's
|
|
2076
|
+
// polygon-cut box drew as a solid diamond; the emitter
|
|
2077
|
+
// now drops every clip-path). daisy's drawn polygon is an
|
|
2078
|
+
// L inside the content box: a bar at x 50–70% over the
|
|
2079
|
+
// full height and a foot at y 80–100% from x 20%, both
|
|
2080
|
+
// 20% of the box thick, rotated 45° about the box's
|
|
2081
|
+
// centre. That is exactly the classic two-border check:
|
|
2082
|
+
// a 50%-wide full-height box at x 20% with a right and a
|
|
2083
|
+
// bottom border 20% of the content box thick, turned
|
|
2084
|
+
// about the content box's centre (60% 50% of itself).
|
|
2085
|
+
//
|
|
2086
|
+
// The ink: the control's `color:` names
|
|
2087
|
+
// `--checkbox-on-accent`, and `currentColor` was that ink
|
|
2088
|
+
// by inheritance (it never resolves on lynx,
|
|
2089
|
+
// signalxjs/lynx#1079) — spent directly on the borders.
|
|
2025
2090
|
//
|
|
2026
|
-
// The
|
|
2027
|
-
//
|
|
2028
|
-
//
|
|
2029
|
-
//
|
|
2030
|
-
// proven on both platforms — and the transition follows
|
|
2031
|
-
// the property that now moves.
|
|
2091
|
+
// The motion is `transform`: the standalone `rotate` /
|
|
2092
|
+
// `translate` properties resolve on iOS but not on
|
|
2093
|
+
// Android (signalxjs/lynx#1084). No sheen shadow — on a
|
|
2094
|
+
// hollow box it would band the inside of the L.
|
|
2032
2095
|
base: {
|
|
2033
|
-
|
|
2096
|
+
boxSizing: 'border-box',
|
|
2097
|
+
width: '50%',
|
|
2098
|
+
height: '100%',
|
|
2099
|
+
marginLeft: '20%',
|
|
2100
|
+
backgroundColor: 'transparent',
|
|
2101
|
+
borderStyle: 'solid',
|
|
2102
|
+
borderColor: 'var(--checkbox-on-accent)',
|
|
2103
|
+
borderTopWidth: '0',
|
|
2104
|
+
borderLeftWidth: '0',
|
|
2105
|
+
borderRightWidth: CHECKBOX_LYNX_STROKE,
|
|
2106
|
+
borderBottomWidth: CHECKBOX_LYNX_STROKE,
|
|
2107
|
+
boxShadow: 'none',
|
|
2108
|
+
transformOrigin: '60% 50%',
|
|
2034
2109
|
transform: 'rotate(45deg)',
|
|
2035
|
-
transition: '
|
|
2036
|
-
+ 'opacity var(--duration-instant) var(--ease-standard) var(--duration-instant), '
|
|
2110
|
+
transition: 'opacity var(--duration-instant) var(--ease-standard) var(--duration-instant), '
|
|
2037
2111
|
+ 'transform var(--duration-slow) var(--ease-standard) var(--duration-instant)',
|
|
2038
2112
|
},
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2113
|
+
states: {
|
|
2114
|
+
// daisy's dash (signalxjs/lynx#1217): the same six
|
|
2115
|
+
// points un-rotated into a bar at x 20–80%, 20% of
|
|
2116
|
+
// the box tall, lifted to the middle. Here a filled,
|
|
2117
|
+
// borderless 60% × 20% box centred on the cross
|
|
2118
|
+
// axis — the unclipped full-height box had painted
|
|
2119
|
+
// the top half of the control.
|
|
2120
|
+
indeterminate: {
|
|
2121
|
+
width: '60%',
|
|
2122
|
+
height: '20%',
|
|
2123
|
+
alignSelf: 'center',
|
|
2124
|
+
borderRightWidth: '0',
|
|
2125
|
+
borderBottomWidth: '0',
|
|
2126
|
+
backgroundColor: 'var(--checkbox-on-accent)',
|
|
2127
|
+
transform: 'rotate(0deg)',
|
|
2128
|
+
},
|
|
2129
|
+
},
|
|
2042
2130
|
},
|
|
2043
2131
|
},
|
|
2044
2132
|
},
|
|
@@ -2064,6 +2152,11 @@ export const radioGroup: RecipeInput = {
|
|
|
2064
2152
|
// `invalid` paints on each `item-control`, which carries the flag
|
|
2065
2153
|
// itself (#267) — the root only groups the items.
|
|
2066
2154
|
states: { invalid: {}, required: {} },
|
|
2155
|
+
// `orientation="horizontal"` lays the items in a wrapping row —
|
|
2156
|
+
// checkbox-group's rule (signalxjs/zero#393).
|
|
2157
|
+
selectors: {
|
|
2158
|
+
'&[data-orientation="horizontal"]': { flexDirection: 'row', flexWrap: 'wrap', columnGap: 'var(--space-lg)' },
|
|
2159
|
+
},
|
|
2067
2160
|
},
|
|
2068
2161
|
label: {
|
|
2069
2162
|
base: { fontSize: 'var(--text-sm)', fontWeight: 'var(--weight-semibold)' },
|
|
@@ -2171,9 +2264,20 @@ export const radioGroup: RecipeInput = {
|
|
|
2171
2264
|
targets: {
|
|
2172
2265
|
lynx: {
|
|
2173
2266
|
parts: {
|
|
2267
|
+
// The control/label row, pinned — as checkbox's root
|
|
2268
|
+
// (signalxjs/lynx#1203).
|
|
2269
|
+
item: { base: { display: 'flex', flexDirection: 'row' } },
|
|
2174
2270
|
'item-control': {
|
|
2175
|
-
|
|
2176
|
-
|
|
2271
|
+
// `display: flex` in place of `inline-block`, which lynx
|
|
2272
|
+
// does not have — the padding still insets the dot.
|
|
2273
|
+
base: { display: 'flex', border: 'var(--border) solid var(--radio-accent)' },
|
|
2274
|
+
states: {
|
|
2275
|
+
checked: { borderColor: 'var(--radio-accent)' },
|
|
2276
|
+
// The round ring (signalxjs/lynx#1163): spread
|
|
2277
|
+
// shadows follow the 9999px radius where `outline`
|
|
2278
|
+
// drew a square.
|
|
2279
|
+
'focus-visible': lynxFocusRing('var(--radio-accent)', { under: '0 1px var(--depth-shade) inset' }),
|
|
2280
|
+
},
|
|
2177
2281
|
},
|
|
2178
2282
|
'item-indicator': {
|
|
2179
2283
|
states: { checked: { backgroundColor: 'var(--radio-accent)' } },
|
|
@@ -4088,6 +4192,11 @@ export const combobox: RecipeInput = {
|
|
|
4088
4192
|
flexWrap: 'wrap',
|
|
4089
4193
|
minWidth: '13rem',
|
|
4090
4194
|
...fieldControl,
|
|
4195
|
+
// The one field control that wraps: under `multiple` its tags
|
|
4196
|
+
// flow onto new rows, so the field step is a floor, not a cap —
|
|
4197
|
+
// a fixed height spills wrapped tags over the next field (#407).
|
|
4198
|
+
height: 'auto',
|
|
4199
|
+
minHeight: fieldHeight('md'),
|
|
4091
4200
|
transition: 'border-color var(--duration-fast) var(--ease-standard)',
|
|
4092
4201
|
},
|
|
4093
4202
|
states: {
|
|
@@ -4394,8 +4503,52 @@ export const toggle: RecipeInput = {
|
|
|
4394
4503
|
},
|
|
4395
4504
|
},
|
|
4396
4505
|
defaultVariants: { color: 'primary', size: 'md' },
|
|
4506
|
+
// signalxjs/lynx#1204 — the lynx replacements for what this recipe's
|
|
4507
|
+
// shared sections cannot say there.
|
|
4508
|
+
targets: {
|
|
4509
|
+
lynx: {
|
|
4510
|
+
parts: {
|
|
4511
|
+
root: {
|
|
4512
|
+
// daisy's content-sized, never-squeezed btn (lynx#1165):
|
|
4513
|
+
// a lynx view has no inline formatting context, so the
|
|
4514
|
+
// toggle stretched to its column and a squeezed label
|
|
4515
|
+
// wrapped per character. The glyph + label sit in a row.
|
|
4516
|
+
// The hairline as longhands: a var-bearing `border`
|
|
4517
|
+
// shorthand expands after the cascade and would beat
|
|
4518
|
+
// any edge a later rule restates (lynx#1161/#1162).
|
|
4519
|
+
base: {
|
|
4520
|
+
...lynxBtnFit,
|
|
4521
|
+
flexDirection: 'row',
|
|
4522
|
+
border: '0 solid transparent',
|
|
4523
|
+
...lynxSides('border{}Width', 'var(--border)'),
|
|
4524
|
+
...lynxSides('border{}Color', 'var(--color-base-300)'),
|
|
4525
|
+
},
|
|
4526
|
+
states: {
|
|
4527
|
+
// No hover on a touch platform: the held toggle
|
|
4528
|
+
// takes the wash daisy's hover gives it. The shared
|
|
4529
|
+
// pressed rule adds the btn's 1px sink.
|
|
4530
|
+
pressed: { background: 'var(--color-base-300)' },
|
|
4531
|
+
on: lynxSides('border{}Color', 'var(--toggle-accent)'),
|
|
4532
|
+
// lynx's `outline` ignores the radius and has no
|
|
4533
|
+
// offset (lynx#1163): the ring as spread shadows.
|
|
4534
|
+
'focus-visible': lynxFocusRing('var(--toggle-accent)'),
|
|
4535
|
+
},
|
|
4536
|
+
selectors: {
|
|
4537
|
+
// A held on toggle keeps its fill (the web's
|
|
4538
|
+
// `[data-state=on]:hover` stays on the accent too).
|
|
4539
|
+
'&[data-state="on"][data-pressed]': { background: 'var(--toggle-accent)' },
|
|
4540
|
+
},
|
|
4541
|
+
},
|
|
4542
|
+
},
|
|
4543
|
+
},
|
|
4544
|
+
},
|
|
4397
4545
|
};
|
|
4398
4546
|
|
|
4547
|
+
/** A toggle-group end item's outer corner: the frame's radius inside its hairline. */
|
|
4548
|
+
const joinInnerRadius = 'calc(var(--radius-field) - var(--border))';
|
|
4549
|
+
/** A zero seam width that still holds var(), so it can beat the var-bearing seam on lynx. */
|
|
4550
|
+
const noSeam = 'calc(var(--border) * 0)';
|
|
4551
|
+
|
|
4399
4552
|
// daisy "join" of btns: one bordered capsule, hairline seams between items,
|
|
4400
4553
|
// the on item filled with the accent.
|
|
4401
4554
|
export const toggleGroup: RecipeInput = {
|
|
@@ -4505,6 +4658,101 @@ export const toggleGroup: RecipeInput = {
|
|
|
4505
4658
|
),
|
|
4506
4659
|
},
|
|
4507
4660
|
defaultVariants: { color: 'primary' },
|
|
4661
|
+
// signalxjs/lynx#1204 — the lynx replacements for what this recipe's
|
|
4662
|
+
// shared sections cannot say there.
|
|
4663
|
+
targets: {
|
|
4664
|
+
lynx: {
|
|
4665
|
+
parts: {
|
|
4666
|
+
root: {
|
|
4667
|
+
// Content-sized like the web's inline-flex capsule (a
|
|
4668
|
+
// lynx view stretches to its column otherwise), a row
|
|
4669
|
+
// unless vertical, and the frame as longhands so the
|
|
4670
|
+
// orientation rules below can drop one edge by
|
|
4671
|
+
// specificity (a var-bearing `border` shorthand would
|
|
4672
|
+
// expand over them, lynx#1161/#1162).
|
|
4673
|
+
base: {
|
|
4674
|
+
width: 'max-content',
|
|
4675
|
+
flexShrink: '0',
|
|
4676
|
+
flexDirection: 'row',
|
|
4677
|
+
border: '0 solid transparent',
|
|
4678
|
+
...lynxSides('border{}Width', 'var(--border)'),
|
|
4679
|
+
...lynxSides('border{}Color', 'var(--color-base-300)'),
|
|
4680
|
+
},
|
|
4681
|
+
// The frame keeps all four edges (signalxjs/lynx#1218).
|
|
4682
|
+
// It used to give up its leading edge to the first item's
|
|
4683
|
+
// seam and trust its own clip to round that item's
|
|
4684
|
+
// corners, but on iOS the frame's clip does not round the
|
|
4685
|
+
// leading corners: the first item painted square, and its
|
|
4686
|
+
// seam was lost with them. The `first`/`last` modifiers
|
|
4687
|
+
// below carry the join's ends instead.
|
|
4688
|
+
},
|
|
4689
|
+
item: {
|
|
4690
|
+
base: { flexShrink: '0', flexWrap: 'nowrap' },
|
|
4691
|
+
states: {
|
|
4692
|
+
// No hover on a touch platform: a held item takes the
|
|
4693
|
+
// wash daisy's hover gives it; an on item keeps its
|
|
4694
|
+
// fill (the selector below).
|
|
4695
|
+
pressed: { background: 'var(--color-base-300)' },
|
|
4696
|
+
// The frame clips its items, so the ring is drawn
|
|
4697
|
+
// inside the item (lynx's `outline` has no offset
|
|
4698
|
+
// and ignores the radius, lynx#1163).
|
|
4699
|
+
'focus-visible': lynxFocusRing('var(--toggle-group-accent)', { inset: true }),
|
|
4700
|
+
},
|
|
4701
|
+
selectors: {
|
|
4702
|
+
'&[data-state="on"][data-pressed]': { background: 'var(--toggle-group-accent)' },
|
|
4703
|
+
// The join seams: lynx has no sibling combinator, so
|
|
4704
|
+
// the web's `item + item` edge cannot be selected.
|
|
4705
|
+
// Every item draws its leading seam, and the `first`
|
|
4706
|
+
// modifier takes it off the item at the frame's edge.
|
|
4707
|
+
'&[data-orientation="horizontal"]': {
|
|
4708
|
+
borderLeftWidth: 'var(--border)',
|
|
4709
|
+
borderLeftStyle: 'solid',
|
|
4710
|
+
borderLeftColor: 'var(--color-base-300)',
|
|
4711
|
+
},
|
|
4712
|
+
'&[data-orientation="vertical"]': {
|
|
4713
|
+
borderTopWidth: 'var(--border)',
|
|
4714
|
+
borderTopStyle: 'solid',
|
|
4715
|
+
borderTopColor: 'var(--color-base-300)',
|
|
4716
|
+
},
|
|
4717
|
+
},
|
|
4718
|
+
},
|
|
4719
|
+
},
|
|
4720
|
+
// The ends of the join (signalxjs/lynx#1218). Lynx has no
|
|
4721
|
+
// `:first-child` or `:last-child`, so lynx-zero stamps the end
|
|
4722
|
+
// items itself, as `data-mod-first` / `data-mod-last` (the
|
|
4723
|
+
// `zx-m-first` / `zx-m-last` classes). They are a lynx-zero
|
|
4724
|
+
// rendering detail, not modifiers an author sets, so they are
|
|
4725
|
+
// not declared in the vocabulary and are styled from raw lynx
|
|
4726
|
+
// css. The end items round their own outer corners to the
|
|
4727
|
+
// frame's inner radius, so an on or held end item follows the
|
|
4728
|
+
// frame's corners without relying on its clip; `first` also
|
|
4729
|
+
// drops the leading seam, which would double the frame's edge.
|
|
4730
|
+
// That zero width is spelled through var() on purpose: a static
|
|
4731
|
+
// longhand loses to the var-bearing seam width whatever its
|
|
4732
|
+
// specificity (lynx expands var() declarations after the
|
|
4733
|
+
// cascade, signalxjs/lynx#1161).
|
|
4734
|
+
css: `
|
|
4735
|
+
.zx-toggle-group__item.zx-m-first.zx-o-horizontal {
|
|
4736
|
+
border-left-width: ${noSeam};
|
|
4737
|
+
border-top-left-radius: ${joinInnerRadius};
|
|
4738
|
+
border-bottom-left-radius: ${joinInnerRadius};
|
|
4739
|
+
}
|
|
4740
|
+
.zx-toggle-group__item.zx-m-first.zx-o-vertical {
|
|
4741
|
+
border-top-width: ${noSeam};
|
|
4742
|
+
border-top-left-radius: ${joinInnerRadius};
|
|
4743
|
+
border-top-right-radius: ${joinInnerRadius};
|
|
4744
|
+
}
|
|
4745
|
+
.zx-toggle-group__item.zx-m-last.zx-o-horizontal {
|
|
4746
|
+
border-top-right-radius: ${joinInnerRadius};
|
|
4747
|
+
border-bottom-right-radius: ${joinInnerRadius};
|
|
4748
|
+
}
|
|
4749
|
+
.zx-toggle-group__item.zx-m-last.zx-o-vertical {
|
|
4750
|
+
border-bottom-left-radius: ${joinInnerRadius};
|
|
4751
|
+
border-bottom-right-radius: ${joinInnerRadius};
|
|
4752
|
+
}
|
|
4753
|
+
`,
|
|
4754
|
+
},
|
|
4755
|
+
},
|
|
4508
4756
|
};
|
|
4509
4757
|
|
|
4510
4758
|
// daisy "join" of an input and two btns: one bordered capsule (the control),
|
|
@@ -4649,14 +4897,65 @@ export const numberInput: RecipeInput = {
|
|
|
4649
4897
|
xl: { control: { base: { height: fieldHeight('xl') } }, input: { base: { fontSize: 'var(--text-lg)', padding: 'var(--space-lg) var(--space-xl)' } } },
|
|
4650
4898
|
},
|
|
4651
4899
|
},
|
|
4652
|
-
//
|
|
4653
|
-
//
|
|
4654
|
-
//
|
|
4900
|
+
// Lynx restatements:
|
|
4901
|
+
// - the steppers' inline padding and their seams, physically: logical
|
|
4902
|
+
// spellings resolve on iOS but not on Android (measured,
|
|
4903
|
+
// signalxjs/lynx#1084);
|
|
4904
|
+
// - the steppers are views around a text glyph, with none of a
|
|
4905
|
+
// button's content centering;
|
|
4906
|
+
// - the native input takes no `inherit`: its ink is spelled out, and it
|
|
4907
|
+
// grows into any room a stretched control leaves between the steppers;
|
|
4908
|
+
// - the web root is inline-flex and hugs its content; lynx's flex column
|
|
4909
|
+
// stretches, so the children align to the start instead;
|
|
4910
|
+
// - the focus ring is `lynxFocusRing` (lynx's outline ignores the radius
|
|
4911
|
+
// and has no offset, signalxjs/lynx#1163). A shadow must not be
|
|
4912
|
+
// clipped by the box's own `overflow: hidden`, so the control stops
|
|
4913
|
+
// clipping and the steppers round their own outer corners instead.
|
|
4655
4914
|
targets: {
|
|
4656
4915
|
lynx: {
|
|
4657
4916
|
parts: {
|
|
4658
|
-
|
|
4659
|
-
|
|
4917
|
+
root: { base: { alignItems: 'flex-start' } },
|
|
4918
|
+
control: {
|
|
4919
|
+
base: { overflow: 'visible' },
|
|
4920
|
+
states: { 'focus-visible': lynxFocusRing('var(--number-input-accent)') },
|
|
4921
|
+
},
|
|
4922
|
+
// `::placeholder` does not exist on lynx, so the web's 60%
|
|
4923
|
+
// mix rides lynx's own `-x-placeholder-color` (baked per
|
|
4924
|
+
// theme) — without it the platform default reads dark on
|
|
4925
|
+
// dark (signalxjs/lynx#1222). Spelled out, not the
|
|
4926
|
+
// `textFieldPlaceholder` const: that one is declared further
|
|
4927
|
+
// down the module, after this literal is evaluated.
|
|
4928
|
+
input: {
|
|
4929
|
+
base: {
|
|
4930
|
+
color: 'var(--color-base-content)',
|
|
4931
|
+
'-x-placeholder-color': 'color-mix(in oklab, var(--color-base-content) 60%, transparent)',
|
|
4932
|
+
flexGrow: '1',
|
|
4933
|
+
},
|
|
4934
|
+
},
|
|
4935
|
+
'increment-trigger': {
|
|
4936
|
+
base: {
|
|
4937
|
+
display: 'flex',
|
|
4938
|
+
alignItems: 'center',
|
|
4939
|
+
justifyContent: 'center',
|
|
4940
|
+
paddingLeft: 'var(--space-lg)',
|
|
4941
|
+
paddingRight: 'var(--space-lg)',
|
|
4942
|
+
borderLeft: 'var(--border) solid var(--color-base-300)',
|
|
4943
|
+
borderTopRightRadius: 'var(--radius-field)',
|
|
4944
|
+
borderBottomRightRadius: 'var(--radius-field)',
|
|
4945
|
+
},
|
|
4946
|
+
},
|
|
4947
|
+
'decrement-trigger': {
|
|
4948
|
+
base: {
|
|
4949
|
+
display: 'flex',
|
|
4950
|
+
alignItems: 'center',
|
|
4951
|
+
justifyContent: 'center',
|
|
4952
|
+
paddingLeft: 'var(--space-lg)',
|
|
4953
|
+
paddingRight: 'var(--space-lg)',
|
|
4954
|
+
borderRight: 'var(--border) solid var(--color-base-300)',
|
|
4955
|
+
borderTopLeftRadius: 'var(--radius-field)',
|
|
4956
|
+
borderBottomLeftRadius: 'var(--radius-field)',
|
|
4957
|
+
},
|
|
4958
|
+
},
|
|
4660
4959
|
},
|
|
4661
4960
|
},
|
|
4662
4961
|
},
|
|
@@ -5070,6 +5369,15 @@ const affixSize = (fontSize: string) => ({
|
|
|
5070
5369
|
'visibility-trigger': { base: { fontSize } },
|
|
5071
5370
|
});
|
|
5072
5371
|
|
|
5372
|
+
/**
|
|
5373
|
+
* A text field's placeholder ink on lynx (signalxjs/lynx#1205): the web's
|
|
5374
|
+
* `::placeholder` mix, for lynx's `-x-placeholder-color` property — the
|
|
5375
|
+
* emitter bakes the `color-mix()` to one literal per theme.
|
|
5376
|
+
*/
|
|
5377
|
+
const textFieldPlaceholder = 'color-mix(in oklab, var(--color-base-content) 60%, transparent)';
|
|
5378
|
+
/** The ghost field button's held look on lynx — the web's hover wash, on a press. */
|
|
5379
|
+
const fieldButtonHeld: CssProps = { color: 'var(--color-base-content)', background: 'var(--color-base-200)' };
|
|
5380
|
+
|
|
5073
5381
|
/**
|
|
5074
5382
|
* daisy "input" flavor: the shared field box with nothing in it but the text.
|
|
5075
5383
|
* Same `fieldControl` metrics as select's trigger, combobox's control and the
|
|
@@ -5191,8 +5499,59 @@ export const input: RecipeInput = {
|
|
|
5191
5499
|
xl: { control: { base: { height: fieldHeight('xl') } }, input: { base: { fontSize: 'var(--text-lg)', padding: 'var(--space-lg) var(--space-xl)' } }, ...affixSize('var(--text-lg)') },
|
|
5192
5500
|
},
|
|
5193
5501
|
},
|
|
5502
|
+
// lynx (signalxjs/lynx#1205). The field is a native text widget inside
|
|
5503
|
+
// a view `control`, so what the web gets from the cascade is restated:
|
|
5504
|
+
targets: {
|
|
5505
|
+
lynx: {
|
|
5506
|
+
parts: {
|
|
5507
|
+
// The web control is an `inline-flex` row; the emitter's
|
|
5508
|
+
// `flex` rewrite keeps no direction, so the row is explicit.
|
|
5509
|
+
// No `:hover` border on a touch platform — the ring below
|
|
5510
|
+
// (box-shadow spread, signalxjs/lynx#1163) is the focus cue,
|
|
5511
|
+
// in the root's accent like the web outline.
|
|
5512
|
+
control: {
|
|
5513
|
+
base: { flexDirection: 'row' },
|
|
5514
|
+
states: { 'focus-visible': lynxFocusRing('var(--input-accent)') },
|
|
5515
|
+
},
|
|
5516
|
+
// `color: inherit` never reaches a native text field, and
|
|
5517
|
+
// `::placeholder` does not exist: the ink is explicit and the
|
|
5518
|
+
// placeholder takes lynx's own `-x-placeholder-color`, the
|
|
5519
|
+
// web's 60% mix baked per theme.
|
|
5520
|
+
input: {
|
|
5521
|
+
base: {
|
|
5522
|
+
color: 'var(--color-base-content)',
|
|
5523
|
+
'-x-placeholder-color': textFieldPlaceholder,
|
|
5524
|
+
flexGrow: '1',
|
|
5525
|
+
flexShrink: '1',
|
|
5526
|
+
},
|
|
5527
|
+
},
|
|
5528
|
+
// The adornment's edge padding, physical: Android never
|
|
5529
|
+
// resolves the logical spellings (signalxjs/lynx#1084).
|
|
5530
|
+
adornment: {
|
|
5531
|
+
selectors: {
|
|
5532
|
+
'&[data-placement="start"]': { paddingLeft: 'var(--space-md)' },
|
|
5533
|
+
'&[data-placement="end"]': { paddingRight: 'var(--space-md)' },
|
|
5534
|
+
},
|
|
5535
|
+
},
|
|
5536
|
+
// Same for the ghost buttons' end margin; the web's `:hover`
|
|
5537
|
+
// wash answers a press instead.
|
|
5538
|
+
'clear-trigger': {
|
|
5539
|
+
base: { marginRight: 'var(--space-xs)' },
|
|
5540
|
+
states: { pressed: fieldButtonHeld },
|
|
5541
|
+
},
|
|
5542
|
+
'visibility-trigger': {
|
|
5543
|
+
base: { marginRight: 'var(--space-xs)' },
|
|
5544
|
+
states: { pressed: fieldButtonHeld },
|
|
5545
|
+
},
|
|
5546
|
+
},
|
|
5547
|
+
},
|
|
5548
|
+
},
|
|
5194
5549
|
};
|
|
5195
5550
|
|
|
5551
|
+
/** The lynx textarea floor: two field heights, as ONE calc() (signalxjs/lynx#1219). */
|
|
5552
|
+
const textareaFloor = (step: keyof typeof FIELD_STEPS): string =>
|
|
5553
|
+
`calc(var(--size-field) * ${FIELD_STEPS[step] * 2})`;
|
|
5554
|
+
|
|
5196
5555
|
/**
|
|
5197
5556
|
* daisy "textarea" flavor. The `fieldControl` box minus its `height`: the
|
|
5198
5557
|
* whole point of a textarea is that its height is the content's (and the
|
|
@@ -5259,6 +5618,64 @@ export const textarea: RecipeInput = {
|
|
|
5259
5618
|
xl: { textarea: { base: { fontSize: 'var(--text-lg)', minHeight: `calc(${fieldHeight('xl')} * 2)`, padding: 'var(--space-lg) var(--space-xl)' } } },
|
|
5260
5619
|
},
|
|
5261
5620
|
},
|
|
5621
|
+
// lynx (signalxjs/lynx#1205): lynx-zero renders the part as a view (the
|
|
5622
|
+
// box, whose state classes repaint) around the native field, which wears
|
|
5623
|
+
// the part's base class as its text face. So the ink and the placeholder
|
|
5624
|
+
// are explicit here — `color: inherit` and `::placeholder` never reach a
|
|
5625
|
+
// native widget — and the web's `:hover` border and outline ring become
|
|
5626
|
+
// the lynx focus ring in the root's accent (signalxjs/lynx#1163).
|
|
5627
|
+
//
|
|
5628
|
+
// The floor is one flat `calc(var(--size-field) * n)` per size
|
|
5629
|
+
// (signalxjs/lynx#1219): the shared `calc(fieldHeight * 2)` nests a
|
|
5630
|
+
// calc() in a calc(), and on device the box never took it — an empty
|
|
5631
|
+
// textarea sat one line tall.
|
|
5632
|
+
//
|
|
5633
|
+
// The focus ring is NOT `lynxFocusRing`'s two stacked outset shadows
|
|
5634
|
+
// (signalxjs/lynx#1220). Lynx iOS re-inserts a view's outset shadow
|
|
5635
|
+
// layers at a stale index whenever it re-lays the view out
|
|
5636
|
+
// (`LynxBackgroundManager.updateShadow` reads the sublayer count before
|
|
5637
|
+
// removing the old layers), which reverses their stacking: the 4px ring
|
|
5638
|
+
// lands on top of the 2px gap and paints one solid band. A fixed-height
|
|
5639
|
+
// box never re-lays out; this auto-height one does, as its native field
|
|
5640
|
+
// measures and grows. So the ring is spelled with one outset shadow and
|
|
5641
|
+
// one inset shadow, which paint disjoint areas and cannot swap: the ring
|
|
5642
|
+
// just outside the box, the box's own border repainted as the base-100
|
|
5643
|
+
// gap, and the hairline redrawn inside it by the inset spread, in the
|
|
5644
|
+
// edge colour (`--textarea-edge`, the error colour while invalid). The
|
|
5645
|
+
// gap is the border's width instead of the web's 2px.
|
|
5646
|
+
targets: {
|
|
5647
|
+
lynx: {
|
|
5648
|
+
parts: {
|
|
5649
|
+
textarea: {
|
|
5650
|
+
base: {
|
|
5651
|
+
color: 'var(--color-base-content)',
|
|
5652
|
+
'-x-placeholder-color': textFieldPlaceholder,
|
|
5653
|
+
'--textarea-edge': 'var(--color-base-300)',
|
|
5654
|
+
minHeight: textareaFloor('md'),
|
|
5655
|
+
},
|
|
5656
|
+
states: {
|
|
5657
|
+
invalid: { '--textarea-edge': 'var(--color-error)' },
|
|
5658
|
+
'focus-visible': {
|
|
5659
|
+
outline: 'none',
|
|
5660
|
+
borderColor: 'var(--color-base-100)',
|
|
5661
|
+
boxShadow: '0 0 0 2px var(--textarea-accent), '
|
|
5662
|
+
// Twice the border (1px in every shipped
|
|
5663
|
+
// theme): the inner half shows as the hairline.
|
|
5664
|
+
+ 'inset 0 0 0 2px var(--textarea-edge)',
|
|
5665
|
+
},
|
|
5666
|
+
},
|
|
5667
|
+
},
|
|
5668
|
+
},
|
|
5669
|
+
variants: {
|
|
5670
|
+
size: {
|
|
5671
|
+
xs: { textarea: { base: { minHeight: textareaFloor('xs') } } },
|
|
5672
|
+
sm: { textarea: { base: { minHeight: textareaFloor('sm') } } },
|
|
5673
|
+
lg: { textarea: { base: { minHeight: textareaFloor('lg') } } },
|
|
5674
|
+
xl: { textarea: { base: { minHeight: textareaFloor('xl') } } },
|
|
5675
|
+
},
|
|
5676
|
+
},
|
|
5677
|
+
},
|
|
5678
|
+
},
|
|
5262
5679
|
};
|
|
5263
5680
|
|
|
5264
5681
|
// ── Content tier (#311) ───────────────────────────────────────────────────
|