@sigx/zero-daisyui 0.11.0 → 0.12.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/dist/recipes.js CHANGED
@@ -1621,6 +1621,10 @@ export const field = {
1621
1621
  label: {
1622
1622
  base: { fontSize: 'var(--text-sm)', fontWeight: 'var(--weight-semibold)', color: 'var(--field-accent)' },
1623
1623
  states: { disabled: { opacity: 'var(--disabled-opacity)' } },
1624
+ // lynx has no pseudo-elements and the anatomy no part to carry
1625
+ // the asterisk, so it stays a web-only mark: on lynx a required
1626
+ // field still stamps `required` on its root and label, and the
1627
+ // control inside adopts it (signalxjs/lynx#1205).
1624
1628
  selectors: {
1625
1629
  '&[data-required]::after': { content: '" *"', color: roleInk('error') },
1626
1630
  },
@@ -1711,6 +1715,33 @@ export const fieldset = {
1711
1715
  xl: { legend: { base: { fontSize: 'var(--text-lg)' } } },
1712
1716
  },
1713
1717
  },
1718
+ // Lynx has no grid: the one-column track becomes a flex column (the gap
1719
+ // survives), and the block/inline paddings are restated physically —
1720
+ // logical spellings resolve on iOS but not on Android (signalxjs/lynx#1084).
1721
+ targets: {
1722
+ lynx: {
1723
+ parts: {
1724
+ root: {
1725
+ base: {
1726
+ display: 'flex',
1727
+ flexDirection: 'column',
1728
+ paddingTop: 'var(--space-xs)',
1729
+ paddingBottom: 'var(--space-xs)',
1730
+ paddingLeft: '0',
1731
+ paddingRight: '0',
1732
+ },
1733
+ },
1734
+ legend: {
1735
+ base: {
1736
+ paddingTop: 'var(--space-sm)',
1737
+ paddingBottom: 'var(--space-sm)',
1738
+ paddingLeft: '0',
1739
+ paddingRight: '0',
1740
+ },
1741
+ },
1742
+ },
1743
+ },
1744
+ },
1714
1745
  };
1715
1746
  /**
1716
1747
  * daisy's tick, drawn by a clip-path that GROWS.
@@ -1939,11 +1970,25 @@ export const checkbox = {
1939
1970
  targets: {
1940
1971
  lynx: {
1941
1972
  parts: {
1973
+ // The box/label row, pinned: `inline-flex` is rewritten to
1974
+ // `flex`, and the row must not depend on the engine's
1975
+ // default direction (signalxjs/lynx#1203).
1976
+ root: { base: { display: 'flex', flexDirection: 'row' } },
1942
1977
  control: {
1943
1978
  // The web fallback (`color-mix(in oklab, … currentColor …)`)
1944
1979
  // exists for consumers that unset the accent; this package
1945
1980
  // always defines it, so the plain accent IS the ring.
1946
- base: { border: 'var(--border) solid var(--checkbox-accent)' },
1981
+ //
1982
+ // `inline-block` is not a lynx display value (no inline
1983
+ // formatting context); a flex box keeps daisy's geometry —
1984
+ // the padding insets the 100%-sized indicator either way.
1985
+ base: { display: 'flex', border: 'var(--border) solid var(--checkbox-accent)' },
1986
+ // The ring as spread shadows that follow the box's radius
1987
+ // (signalxjs/lynx#1163 — lynx's `outline` ignores it and
1988
+ // has no offset), keeping the box's inset depth line.
1989
+ states: {
1990
+ 'focus-visible': lynxFocusRing('var(--checkbox-accent)', { under: '0 1px var(--depth-shade) inset' }),
1991
+ },
1947
1992
  },
1948
1993
  indicator: {
1949
1994
  // The tick's fill: the control's `color:` names
@@ -1989,6 +2034,11 @@ export const radioGroup = {
1989
2034
  // `invalid` paints on each `item-control`, which carries the flag
1990
2035
  // itself (#267) — the root only groups the items.
1991
2036
  states: { invalid: {}, required: {} },
2037
+ // `orientation="horizontal"` lays the items in a wrapping row —
2038
+ // checkbox-group's rule (signalxjs/zero#393).
2039
+ selectors: {
2040
+ '&[data-orientation="horizontal"]': { flexDirection: 'row', flexWrap: 'wrap', columnGap: 'var(--space-lg)' },
2041
+ },
1992
2042
  },
1993
2043
  label: {
1994
2044
  base: { fontSize: 'var(--text-sm)', fontWeight: 'var(--weight-semibold)' },
@@ -2096,9 +2146,20 @@ export const radioGroup = {
2096
2146
  targets: {
2097
2147
  lynx: {
2098
2148
  parts: {
2149
+ // The control/label row, pinned — as checkbox's root
2150
+ // (signalxjs/lynx#1203).
2151
+ item: { base: { display: 'flex', flexDirection: 'row' } },
2099
2152
  'item-control': {
2100
- base: { border: 'var(--border) solid var(--radio-accent)' },
2101
- states: { checked: { borderColor: 'var(--radio-accent)' } },
2153
+ // `display: flex` in place of `inline-block`, which lynx
2154
+ // does not have — the padding still insets the dot.
2155
+ base: { display: 'flex', border: 'var(--border) solid var(--radio-accent)' },
2156
+ states: {
2157
+ checked: { borderColor: 'var(--radio-accent)' },
2158
+ // The round ring (signalxjs/lynx#1163): spread
2159
+ // shadows follow the 9999px radius where `outline`
2160
+ // drew a square.
2161
+ 'focus-visible': lynxFocusRing('var(--radio-accent)', { under: '0 1px var(--depth-shade) inset' }),
2162
+ },
2102
2163
  },
2103
2164
  'item-indicator': {
2104
2165
  states: { checked: { backgroundColor: 'var(--radio-accent)' } },
@@ -4287,6 +4348,45 @@ export const toggle = {
4287
4348
  },
4288
4349
  },
4289
4350
  defaultVariants: { color: 'primary', size: 'md' },
4351
+ // signalxjs/lynx#1204 — the lynx replacements for what this recipe's
4352
+ // shared sections cannot say there.
4353
+ targets: {
4354
+ lynx: {
4355
+ parts: {
4356
+ root: {
4357
+ // daisy's content-sized, never-squeezed btn (lynx#1165):
4358
+ // a lynx view has no inline formatting context, so the
4359
+ // toggle stretched to its column and a squeezed label
4360
+ // wrapped per character. The glyph + label sit in a row.
4361
+ // The hairline as longhands: a var-bearing `border`
4362
+ // shorthand expands after the cascade and would beat
4363
+ // any edge a later rule restates (lynx#1161/#1162).
4364
+ base: {
4365
+ ...lynxBtnFit,
4366
+ flexDirection: 'row',
4367
+ border: '0 solid transparent',
4368
+ ...lynxSides('border{}Width', 'var(--border)'),
4369
+ ...lynxSides('border{}Color', 'var(--color-base-300)'),
4370
+ },
4371
+ states: {
4372
+ // No hover on a touch platform: the held toggle
4373
+ // takes the wash daisy's hover gives it. The shared
4374
+ // pressed rule adds the btn's 1px sink.
4375
+ pressed: { background: 'var(--color-base-300)' },
4376
+ on: lynxSides('border{}Color', 'var(--toggle-accent)'),
4377
+ // lynx's `outline` ignores the radius and has no
4378
+ // offset (lynx#1163): the ring as spread shadows.
4379
+ 'focus-visible': lynxFocusRing('var(--toggle-accent)'),
4380
+ },
4381
+ selectors: {
4382
+ // A held on toggle keeps its fill (the web's
4383
+ // `[data-state=on]:hover` stays on the accent too).
4384
+ '&[data-state="on"][data-pressed]': { background: 'var(--toggle-accent)' },
4385
+ },
4386
+ },
4387
+ },
4388
+ },
4389
+ },
4290
4390
  };
4291
4391
  // daisy "join" of btns: one bordered capsule, hairline seams between items,
4292
4392
  // the on item filled with the accent.
@@ -4395,6 +4495,65 @@ export const toggleGroup = {
4395
4495
  ])),
4396
4496
  },
4397
4497
  defaultVariants: { color: 'primary' },
4498
+ // signalxjs/lynx#1204 — the lynx replacements for what this recipe's
4499
+ // shared sections cannot say there.
4500
+ targets: {
4501
+ lynx: {
4502
+ parts: {
4503
+ root: {
4504
+ // Content-sized like the web's inline-flex capsule (a
4505
+ // lynx view stretches to its column otherwise), a row
4506
+ // unless vertical, and the frame as longhands so the
4507
+ // orientation rules below can drop one edge by
4508
+ // specificity (a var-bearing `border` shorthand would
4509
+ // expand over them, lynx#1161/#1162).
4510
+ base: {
4511
+ width: 'max-content',
4512
+ flexShrink: '0',
4513
+ flexDirection: 'row',
4514
+ border: '0 solid transparent',
4515
+ ...lynxSides('border{}Width', 'var(--border)'),
4516
+ ...lynxSides('border{}Color', 'var(--color-base-300)'),
4517
+ },
4518
+ selectors: {
4519
+ // The join seams: lynx has no sibling combinator, so
4520
+ // the web's `item + item` edge cannot be selected.
4521
+ // Every item draws its leading seam instead (below),
4522
+ // and the frame gives up that edge — the first item's
4523
+ // seam stands in for it, inside the frame's clip.
4524
+ '&[data-orientation="horizontal"]': { borderLeftWidth: '0' },
4525
+ '&[data-orientation="vertical"]': { borderTopWidth: '0' },
4526
+ },
4527
+ },
4528
+ item: {
4529
+ base: { flexShrink: '0', flexWrap: 'nowrap' },
4530
+ states: {
4531
+ // No hover on a touch platform: a held item takes the
4532
+ // wash daisy's hover gives it; an on item keeps its
4533
+ // fill (the selector below).
4534
+ pressed: { background: 'var(--color-base-300)' },
4535
+ // The frame clips its items, so the ring is drawn
4536
+ // inside the item (lynx's `outline` has no offset
4537
+ // and ignores the radius, lynx#1163).
4538
+ 'focus-visible': lynxFocusRing('var(--toggle-group-accent)', { inset: true }),
4539
+ },
4540
+ selectors: {
4541
+ '&[data-state="on"][data-pressed]': { background: 'var(--toggle-group-accent)' },
4542
+ '&[data-orientation="horizontal"]': {
4543
+ borderLeftWidth: 'var(--border)',
4544
+ borderLeftStyle: 'solid',
4545
+ borderLeftColor: 'var(--color-base-300)',
4546
+ },
4547
+ '&[data-orientation="vertical"]': {
4548
+ borderTopWidth: 'var(--border)',
4549
+ borderTopStyle: 'solid',
4550
+ borderTopColor: 'var(--color-base-300)',
4551
+ },
4552
+ },
4553
+ },
4554
+ },
4555
+ },
4556
+ },
4398
4557
  };
4399
4558
  // daisy "join" of an input and two btns: one bordered capsule (the control),
4400
4559
  // hairline seams on the triggers' inner edges, neutral btn fills for the steppers.
@@ -4538,14 +4697,53 @@ export const numberInput = {
4538
4697
  xl: { control: { base: { height: fieldHeight('xl') } }, input: { base: { fontSize: 'var(--text-lg)', padding: 'var(--space-lg) var(--space-xl)' } } },
4539
4698
  },
4540
4699
  },
4541
- // The steppers' inline padding, restated physically: logical spellings
4542
- // resolve on iOS but not on Android (measured, signalxjs/lynx#1084), so
4543
- // the emitter refuses them.
4700
+ // Lynx restatements:
4701
+ // - the steppers' inline padding and their seams, physically: logical
4702
+ // spellings resolve on iOS but not on Android (measured,
4703
+ // signalxjs/lynx#1084);
4704
+ // - the steppers are views around a text glyph, with none of a
4705
+ // button's content centering;
4706
+ // - the native input takes no `inherit`: its ink is spelled out, and it
4707
+ // grows into any room a stretched control leaves between the steppers;
4708
+ // - the web root is inline-flex and hugs its content; lynx's flex column
4709
+ // stretches, so the children align to the start instead;
4710
+ // - the focus ring is `lynxFocusRing` (lynx's outline ignores the radius
4711
+ // and has no offset, signalxjs/lynx#1163). A shadow must not be
4712
+ // clipped by the box's own `overflow: hidden`, so the control stops
4713
+ // clipping and the steppers round their own outer corners instead.
4544
4714
  targets: {
4545
4715
  lynx: {
4546
4716
  parts: {
4547
- 'increment-trigger': { base: { paddingLeft: 'var(--space-lg)', paddingRight: 'var(--space-lg)' } },
4548
- 'decrement-trigger': { base: { paddingLeft: 'var(--space-lg)', paddingRight: 'var(--space-lg)' } },
4717
+ root: { base: { alignItems: 'flex-start' } },
4718
+ control: {
4719
+ base: { overflow: 'visible' },
4720
+ states: { 'focus-visible': lynxFocusRing('var(--number-input-accent)') },
4721
+ },
4722
+ input: { base: { color: 'var(--color-base-content)', flexGrow: '1' } },
4723
+ 'increment-trigger': {
4724
+ base: {
4725
+ display: 'flex',
4726
+ alignItems: 'center',
4727
+ justifyContent: 'center',
4728
+ paddingLeft: 'var(--space-lg)',
4729
+ paddingRight: 'var(--space-lg)',
4730
+ borderLeft: 'var(--border) solid var(--color-base-300)',
4731
+ borderTopRightRadius: 'var(--radius-field)',
4732
+ borderBottomRightRadius: 'var(--radius-field)',
4733
+ },
4734
+ },
4735
+ 'decrement-trigger': {
4736
+ base: {
4737
+ display: 'flex',
4738
+ alignItems: 'center',
4739
+ justifyContent: 'center',
4740
+ paddingLeft: 'var(--space-lg)',
4741
+ paddingRight: 'var(--space-lg)',
4742
+ borderRight: 'var(--border) solid var(--color-base-300)',
4743
+ borderTopLeftRadius: 'var(--radius-field)',
4744
+ borderBottomLeftRadius: 'var(--radius-field)',
4745
+ },
4746
+ },
4549
4747
  },
4550
4748
  },
4551
4749
  },
@@ -4948,6 +5146,14 @@ const affixSize = (fontSize) => ({
4948
5146
  'clear-trigger': { base: { fontSize } },
4949
5147
  'visibility-trigger': { base: { fontSize } },
4950
5148
  });
5149
+ /**
5150
+ * A text field's placeholder ink on lynx (signalxjs/lynx#1205): the web's
5151
+ * `::placeholder` mix, for lynx's `-x-placeholder-color` property — the
5152
+ * emitter bakes the `color-mix()` to one literal per theme.
5153
+ */
5154
+ const textFieldPlaceholder = 'color-mix(in oklab, var(--color-base-content) 60%, transparent)';
5155
+ /** The ghost field button's held look on lynx — the web's hover wash, on a press. */
5156
+ const fieldButtonHeld = { color: 'var(--color-base-content)', background: 'var(--color-base-200)' };
4951
5157
  /**
4952
5158
  * daisy "input" flavor: the shared field box with nothing in it but the text.
4953
5159
  * Same `fieldControl` metrics as select's trigger, combobox's control and the
@@ -5069,6 +5275,53 @@ export const input = {
5069
5275
  xl: { control: { base: { height: fieldHeight('xl') } }, input: { base: { fontSize: 'var(--text-lg)', padding: 'var(--space-lg) var(--space-xl)' } }, ...affixSize('var(--text-lg)') },
5070
5276
  },
5071
5277
  },
5278
+ // lynx (signalxjs/lynx#1205). The field is a native text widget inside
5279
+ // a view `control`, so what the web gets from the cascade is restated:
5280
+ targets: {
5281
+ lynx: {
5282
+ parts: {
5283
+ // The web control is an `inline-flex` row; the emitter's
5284
+ // `flex` rewrite keeps no direction, so the row is explicit.
5285
+ // No `:hover` border on a touch platform — the ring below
5286
+ // (box-shadow spread, signalxjs/lynx#1163) is the focus cue,
5287
+ // in the root's accent like the web outline.
5288
+ control: {
5289
+ base: { flexDirection: 'row' },
5290
+ states: { 'focus-visible': lynxFocusRing('var(--input-accent)') },
5291
+ },
5292
+ // `color: inherit` never reaches a native text field, and
5293
+ // `::placeholder` does not exist: the ink is explicit and the
5294
+ // placeholder takes lynx's own `-x-placeholder-color`, the
5295
+ // web's 60% mix baked per theme.
5296
+ input: {
5297
+ base: {
5298
+ color: 'var(--color-base-content)',
5299
+ '-x-placeholder-color': textFieldPlaceholder,
5300
+ flexGrow: '1',
5301
+ flexShrink: '1',
5302
+ },
5303
+ },
5304
+ // The adornment's edge padding, physical: Android never
5305
+ // resolves the logical spellings (signalxjs/lynx#1084).
5306
+ adornment: {
5307
+ selectors: {
5308
+ '&[data-placement="start"]': { paddingLeft: 'var(--space-md)' },
5309
+ '&[data-placement="end"]': { paddingRight: 'var(--space-md)' },
5310
+ },
5311
+ },
5312
+ // Same for the ghost buttons' end margin; the web's `:hover`
5313
+ // wash answers a press instead.
5314
+ 'clear-trigger': {
5315
+ base: { marginRight: 'var(--space-xs)' },
5316
+ states: { pressed: fieldButtonHeld },
5317
+ },
5318
+ 'visibility-trigger': {
5319
+ base: { marginRight: 'var(--space-xs)' },
5320
+ states: { pressed: fieldButtonHeld },
5321
+ },
5322
+ },
5323
+ },
5324
+ },
5072
5325
  };
5073
5326
  /**
5074
5327
  * daisy "textarea" flavor. The `fieldControl` box minus its `height`: the
@@ -5136,6 +5389,22 @@ export const textarea = {
5136
5389
  xl: { textarea: { base: { fontSize: 'var(--text-lg)', minHeight: `calc(${fieldHeight('xl')} * 2)`, padding: 'var(--space-lg) var(--space-xl)' } } },
5137
5390
  },
5138
5391
  },
5392
+ // lynx (signalxjs/lynx#1205): lynx-zero renders the part as a view (the
5393
+ // box, whose state classes repaint) around the native field, which wears
5394
+ // the part's base class as its text face. So the ink and the placeholder
5395
+ // are explicit here — `color: inherit` and `::placeholder` never reach a
5396
+ // native widget — and the web's `:hover` border and outline ring become
5397
+ // the lynx focus ring in the root's accent (signalxjs/lynx#1163).
5398
+ targets: {
5399
+ lynx: {
5400
+ parts: {
5401
+ textarea: {
5402
+ base: { color: 'var(--color-base-content)', '-x-placeholder-color': textFieldPlaceholder },
5403
+ states: { 'focus-visible': lynxFocusRing('var(--textarea-accent)') },
5404
+ },
5405
+ },
5406
+ },
5407
+ },
5139
5408
  };
5140
5409
  // ── Content tier (#311) ───────────────────────────────────────────────────
5141
5410
  /**