@sigx/zero-daisyui 0.16.0 → 0.17.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sigx/zero-daisyui",
3
- "version": "0.16.0",
3
+ "version": "0.17.0",
4
4
  "description": "DaisyUI-flavored design system for SignalX Zero — daisy's token values and component look as a pure tokens+recipes skin over @sigx/zero primitives. No Tailwind, no daisyUI plugin required.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -62,12 +62,12 @@
62
62
  "url": "https://github.com/signalxjs/zero/issues"
63
63
  },
64
64
  "peerDependencies": {
65
- "@sigx/zero": "^0.16.0"
65
+ "@sigx/zero": "^0.17.0"
66
66
  },
67
67
  "devDependencies": {
68
68
  "typescript": "^7.0.2",
69
- "@sigx/zero-kit": "^0.16.0",
70
- "@sigx/zero": "^0.16.0"
69
+ "@sigx/zero": "^0.17.0",
70
+ "@sigx/zero-kit": "^0.17.0"
71
71
  },
72
72
  "publishConfig": {
73
73
  "access": "public"
package/src/recipes.ts CHANGED
@@ -240,6 +240,98 @@ const disclosureExit = (scope: 'collapsible' | 'accordion'): RecipeInput['target
240
240
  },
241
241
  });
242
242
 
243
+ /**
244
+ * Collapsible's non-native mode (#453): a `<div>` root, a `<button>` trigger
245
+ * and a panel hidden with `hidden="until-found"`. Nothing lives on
246
+ * `::details-content` there, so the panel does that wrapper's work itself:
247
+ * collapsed while `hidden` (the UA's `content-visibility: hidden` keeps the
248
+ * box — padding and border included — on the page), clipped so its content
249
+ * never spills out of a box that is growing or shrinking, and grown
250
+ * from collapsed to `auto` on the open state (`interpolate-size` is the
251
+ * root's, and inherits); the close is `disclosureExit`'s, as in native mode.
252
+ * Inert in native mode: a native panel is never `hidden`, and its block size
253
+ * never moves.
254
+ *
255
+ * The trigger sheds the paint a UA `<button>` has and a `<summary>` never
256
+ * had — only where the recipe's own base leaves a property unset, since this
257
+ * rule (`:where(button)`, the base's specificity) is emitted after it.
258
+ * Web-only, like `hidden="until-found"` itself.
259
+ */
260
+ const BUTTON_RESET: CssProps = {
261
+ appearance: 'none',
262
+ margin: '0',
263
+ padding: '0',
264
+ border: '0',
265
+ background: 'none',
266
+ color: 'inherit',
267
+ fontFamily: 'inherit',
268
+ fontSize: 'inherit',
269
+ fontWeight: 'inherit',
270
+ fontStyle: 'inherit',
271
+ lineHeight: 'inherit',
272
+ letterSpacing: 'inherit',
273
+ textTransform: 'inherit',
274
+ textAlign: 'start',
275
+ inlineSize: '100%',
276
+ boxSizing: 'border-box',
277
+ };
278
+ /** The reset minus what `base` states itself (a `borderRadius` is no border). */
279
+ const summaryLike = (base: CssProps = {}): CssProps => {
280
+ const own = Object.keys(base).filter((key) => !key.endsWith('Radius'));
281
+ return Object.fromEntries(Object.entries(BUTTON_RESET)
282
+ .filter(([key]) => !own.some((k) => k === key || k.startsWith(key))));
283
+ };
284
+ const withNonNative = (recipe: RecipeInput): RecipeInput => {
285
+ const web = recipe.targets?.web ?? {};
286
+ const trigger = web.parts?.trigger ?? {};
287
+ const panel = web.parts?.panel ?? {};
288
+ const reduced = panel.at?.['reduced-motion'] ?? {};
289
+ return {
290
+ ...recipe,
291
+ targets: {
292
+ ...recipe.targets,
293
+ web: {
294
+ ...web,
295
+ parts: {
296
+ ...web.parts,
297
+ trigger: {
298
+ ...trigger,
299
+ selectors: { ...trigger.selectors, '&:where(button)': summaryLike(recipe.parts.trigger?.base) },
300
+ },
301
+ panel: {
302
+ ...panel,
303
+ base: { ...panel.base, overflow: 'clip' },
304
+ selectors: {
305
+ ...panel.selectors,
306
+ '&[data-state="closed"][hidden]': {
307
+ blockSize: '0',
308
+ paddingBlock: '0',
309
+ borderBlockWidth: '0',
310
+ animation: 'none',
311
+ },
312
+ },
313
+ // The open half: from the collapsed box to `auto`.
314
+ states: {
315
+ ...panel.states,
316
+ open: {
317
+ ...panel.states?.open,
318
+ transition: 'block-size var(--duration-normal) var(--ease-standard), padding-block var(--duration-normal) var(--ease-standard)',
319
+ },
320
+ },
321
+ at: {
322
+ ...panel.at,
323
+ 'reduced-motion': {
324
+ ...reduced,
325
+ states: { ...reduced.states, open: { transition: 'none' } },
326
+ },
327
+ },
328
+ },
329
+ },
330
+ },
331
+ },
332
+ };
333
+ };
334
+
243
335
  /**
244
336
  * Merge presence into a part's own styles per KEY, not per block: a recipe
245
337
  * that already writes `states: { open: {} }` — the "deliberately unstyled"
@@ -660,7 +752,7 @@ const disclosureSizes: Record<string, Record<string, PartStyles>> = {
660
752
  };
661
753
 
662
754
  // daisy "collapse collapse-arrow" flavor.
663
- export const collapsible: RecipeInput = {
755
+ export const collapsible: RecipeInput = withNonNative({
664
756
  component: 'collapsible',
665
757
  targets: disclosureExit('collapsible'),
666
758
  // Public to a design system derived from this one (#73).
@@ -739,7 +831,7 @@ export const collapsible: RecipeInput = {
739
831
  // signalxjs/lynx#1070. No `color` default: the un-attributed accent is
740
832
  // base-content ink, outside the color vocabulary.
741
833
  defaultVariants: { size: 'md' },
742
- };
834
+ });
743
835
 
744
836
  // daisy "toggle" flavor.
745
837
  export const switchRecipe: RecipeInput = {
@@ -5489,6 +5581,15 @@ const lynxTreeRowStates: Record<string, CssProps> = {
5489
5581
  ...lynxFocus({ inset: true }),
5490
5582
  };
5491
5583
 
5584
+ /**
5585
+ * The tree rows' lynx base: the fill still eases, the ink snaps — a
5586
+ * transitioned `color` never reaches the row's inheriting label on lynx
5587
+ * (signalxjs/lynx#1292; see the tree-view recipe's `targets`).
5588
+ */
5589
+ const lynxTreeRowBase: CssProps = {
5590
+ transition: 'background var(--duration-fast) var(--ease-standard)',
5591
+ };
5592
+
5492
5593
  /** A held selected row keeps its accent fill (the web's `:not([data-selected])`). */
5493
5594
  const lynxTreeRowSelectors: Record<string, CssProps> = {
5494
5595
  '&[data-selected][data-pressed]': { background: 'var(--tree-accent)' },
@@ -5649,11 +5750,23 @@ export const treeView: RecipeInput = {
5649
5750
  // those carry is restated on the part the runtime stamps. The skin's
5650
5751
  // `:dir(rtl)` mirror and the `@media` stops have no lynx form (no RTL
5651
5752
  // flow, no conditions) and stay dropped.
5753
+ //
5754
+ // The rows transition `background` only (signalxjs/lynx#1292, zero#521).
5755
+ // A row's label is the author's `<text>`, inked by inheritance from the
5756
+ // row. Lynx's new animator (on by default) takes a transitioned `color`
5757
+ // change away from the element's own style pass and ticks it into the
5758
+ // row's paint alone, and the row's children inherit its computed colour
5759
+ // only in that pass, so they kept the OLD ink: a row selected live
5760
+ // painted base-content text on the accent fill, while a row selected at
5761
+ // mount (no transition runs) was right. Read in Lynx's
5762
+ // `fiber_element.cc` (`ConsumeStyleInternal`) and
5763
+ // `css_transition_manager.cc` (`ConsumeCSSProperty`). Without a `color`
5764
+ // transition the new ink is set in the style pass and reaches the label.
5652
5765
  targets: {
5653
5766
  lynx: {
5654
5767
  parts: {
5655
- item: { states: lynxTreeRowStates, selectors: lynxTreeRowSelectors },
5656
- 'branch-trigger': { states: lynxTreeRowStates, selectors: lynxTreeRowSelectors },
5768
+ item: { base: lynxTreeRowBase, states: lynxTreeRowStates, selectors: lynxTreeRowSelectors },
5769
+ 'branch-trigger': { base: lynxTreeRowBase, states: lynxTreeRowStates, selectors: lynxTreeRowSelectors },
5657
5770
  // `inline-block` is not a lynx display value. The glyph is a
5658
5771
  // `<text>` part: a 1em box centred on it, so the quarter turn
5659
5772
  // pivots on the chevron instead of swinging it sideways.
@@ -9395,6 +9508,13 @@ export const table: RecipeInput = {
9395
9508
  base: { flexDirection: 'row' },
9396
9509
  states: { pressed: { color: 'var(--table-accent)' }, ...lynxFocus() },
9397
9510
  },
9511
+ // The mark never takes its width out of the header's word
9512
+ // (signalxjs/lynx#1299): lynx's flex has no automatic
9513
+ // minimum, so the ▲ (kept in layout while unsorted) squeezed
9514
+ // "Name" into "Nam / e" at xl. lynx-zero holds the label's
9515
+ // `<text>` at its content width; a header too narrow for
9516
+ // both overflows whole, as the web's column grows.
9517
+ 'sort-indicator': { base: { flexShrink: '0' } },
9398
9518
  },
9399
9519
  variants: {
9400
9520
  color: Object.fromEntries(ROLES.map((c) => {
@@ -9648,7 +9768,15 @@ export const fileUpload: RecipeInput = {
9648
9768
  base: { alignSelf: 'stretch', display: 'flex', flexDirection: 'column' },
9649
9769
  },
9650
9770
  item: { base: { flexDirection: 'row' } },
9771
+ // Only the name gives way in a narrow row (signalxjs/lynx#1294).
9772
+ // Lynx's flex has no automatic minimum, so every item
9773
+ // shrank below its content: "1.5 kB" wrapped to two lines
9774
+ // and the × squeezed into an oval. The size and the × keep
9775
+ // their content width, as the web's do; the name
9776
+ // (`flex: 1 1 auto; min-width: 0`) ellipsizes.
9777
+ 'item-size': { base: { flexShrink: '0' } },
9651
9778
  'item-remove': {
9779
+ base: { flexShrink: '0' },
9652
9780
  states: { pressed: { background: 'var(--color-base-200)' }, ...lynxFocus() },
9653
9781
  },
9654
9782
  'clear-trigger': {