@pinfold/core 0.1.0 → 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/dist/index.cjs CHANGED
@@ -130,7 +130,7 @@ function setAccordionItem(value, rows, id, open, rules) {
130
130
  if (!row || row.disabled || rules.disabled) return null;
131
131
  const isOpen = value.indexOf(id) !== -1;
132
132
  if (isOpen === open) return null;
133
- if (!open && rules.mode === "single" && !rules.collapsible) return null;
133
+ if (!open && !rules.collapsible && value.length === 1) return null;
134
134
  if (open) return rules.mode === "single" ? [id] : value.concat(id);
135
135
  return value.filter((v) => v !== id);
136
136
  }
@@ -140,7 +140,7 @@ function openAllAccordion(value, rows, rules) {
140
140
  return sameSet(value, next) ? null : next;
141
141
  }
142
142
  function closeAllAccordion(value, rows, rules) {
143
- if (rules.disabled || rules.mode === "single" && !rules.collapsible) return null;
143
+ if (rules.disabled || !rules.collapsible) return null;
144
144
  const locked = new Set(rows.filter((r) => r.disabled).map((r) => r.id));
145
145
  const next = value.filter((id) => locked.has(id));
146
146
  return sameSet(value, next) ? null : next;
@@ -162,6 +162,36 @@ function mapAccordionFields(records, fields) {
162
162
  return out;
163
163
  });
164
164
  }
165
+ var ZERO = /^0*\.?0*(px|rem|em|%|vh|vw)?$/;
166
+ function accordionRowsJoined(variant = "solid", gap) {
167
+ if (gap === void 0 || gap === null || gap === "") return variant !== "separated" && variant !== "filled";
168
+ if (gap === "none" || gap === 0) return true;
169
+ return typeof gap === "string" && ZERO.test(gap.trim());
170
+ }
171
+ function inactiveAccordionOptions(input) {
172
+ var _a;
173
+ const variant = (_a = input.variant) != null ? _a : "solid";
174
+ const joined = accordionRowsJoined(variant, input.gap);
175
+ const card = variant === "solid" || variant === "outline";
176
+ const rows = variant === "separated" || variant === "filled";
177
+ const out = {};
178
+ if (variant === "ghost") {
179
+ out.radius = "Ghost has no surface to round. Pick solid or outline for a rounded container.";
180
+ out.itemRadius = "Ghost rows have no surface to round. Pick separated or filled for rounded rows.";
181
+ }
182
+ if (card) out.itemRadius = "In a solid or outline card the rows are not surfaces; the card takes `radius`.";
183
+ if (rows && joined) out.itemRadius = "With no gap the rows join into one group, rounded by `radius`.";
184
+ if (rows && !joined && input.itemRadius !== void 0) {
185
+ out.radius = 'Separated and filled rows are the surfaces and `itemRadius` already rounds them. Unset `itemRadius` so `radius` reaches the rows, or set `gap: "none"` to join them.';
186
+ }
187
+ if (rows && !joined) out.dividers = 'The rows are already apart. Dividers appear when `gap` is "none".';
188
+ if (input.indicator === "none") {
189
+ out.indicatorPosition = 'There is no indicator (`indicator: "none"`).';
190
+ out.animateIndicator = 'There is no indicator (`indicator: "none"`).';
191
+ }
192
+ if (input.disabled) out.disabledIds = "Every row is already disabled (`disabled: true`).";
193
+ return out;
194
+ }
165
195
  function shouldMountPanel(policy, open, everOpened) {
166
196
  if (policy === "always" || open) return true;
167
197
  return policy === "first-open" && everOpened;
@@ -183,18 +213,20 @@ function resetWarnings() {
183
213
  }
184
214
 
185
215
  // src/index.ts
186
- var version = "0.1.0";
216
+ var version = "0.2.0";
187
217
 
188
218
  exports.__DEV__ = __DEV__;
189
219
  exports.accordionFocusTarget = accordionFocusTarget;
190
220
  exports.accordionPartIds = accordionPartIds;
191
221
  exports.accordionRowId = accordionRowId;
222
+ exports.accordionRowsJoined = accordionRowsJoined;
192
223
  exports.ariaAttr = ariaAttr;
193
224
  exports.closeAllAccordion = closeAllAccordion;
194
225
  exports.cn = cn;
195
226
  exports.composeRefs = composeRefs;
196
227
  exports.dataAttr = dataAttr;
197
228
  exports.dimensionVars = dimensionVars;
229
+ exports.inactiveAccordionOptions = inactiveAccordionOptions;
198
230
  exports.initialAccordionValue = initialAccordionValue;
199
231
  exports.mapAccordionFields = mapAccordionFields;
200
232
  exports.mergeProps = mergeProps;
package/dist/index.d.cts CHANGED
@@ -8,7 +8,7 @@ type PinfoldTheme = 'light' | 'dark' | 'system';
8
8
  /** When a component's code loads. `auto`: light components eagerly, heavy ones when visible. */
9
9
  type PinfoldLazy = 'auto' | 'eager' | 'visible' | 'idle';
10
10
  /** Visual style pack, applied through `data-pinfold-style`. */
11
- type PinfoldStylePack = 'default' | 'glass' | 'bauhaus' | 'brutalist' | 'noise' | 'soft';
11
+ type PinfoldStylePack = 'default' | 'glass' | 'material' | 'noise' | 'brutalist';
12
12
  /** Any DOM props bag a component hands back from a `getXProps()` call. */
13
13
  type PropsBag = Record<string, any>;
14
14
  /** A CSS length. Numbers are px, strings pass through (`'50%'`, `'12rem'`, `calc()`). */
@@ -128,13 +128,13 @@ declare function initialAccordionValue(items: ReadonlyArray<{
128
128
  }>, defaultValue: readonly string[] | undefined, mode: AccordionMode): string[];
129
129
  /**
130
130
  * The open set after asking row `id` to become `open`, or `null` when the
131
- * rules refuse (unknown or disabled row, no change, or closing the only open
132
- * row of a non-collapsible single accordion).
131
+ * rules refuse (unknown or disabled row, no change, or closing the last open
132
+ * row of a non-collapsible accordion, in either mode).
133
133
  */
134
134
  declare function setAccordionItem(value: readonly string[], rows: readonly AccordionRowLike[], id: string, open: boolean, rules: AccordionRules): string[] | null;
135
135
  /** `openAll`: every enabled row, multiple mode only. */
136
136
  declare function openAllAccordion(value: readonly string[], rows: readonly AccordionRowLike[], rules: AccordionRules): string[] | null;
137
- /** `closeAll`: refused for a non-collapsible single accordion. Disabled rows keep their state. */
137
+ /** `closeAll`: refused for a non-collapsible accordion. Disabled rows keep their state. */
138
138
  declare function closeAllAccordion(value: readonly string[], rows: readonly AccordionRowLike[], rules: AccordionRules): string[] | null;
139
139
  interface AccordionKeyOptions {
140
140
  orientation?: PinfoldOrientation;
@@ -150,8 +150,34 @@ declare function accordionFocusTarget(key: string, index: number, count: number,
150
150
  * Field mapping: lets raw records (a CMS row, a DB result) feed the accordion
151
151
  * without a `.map()`. `{ id: 'slug', title: 'question', content: 'answer' }`.
152
152
  */
153
- type AccordionFieldMap = Partial<Record<'id' | 'title' | 'content' | 'description' | 'icon' | 'badge' | 'disabled' | 'defaultOpen', string>>;
153
+ type AccordionFieldMap<Row = any> = Partial<Record<'id' | 'title' | 'content' | 'description' | 'icon' | 'endIcon' | 'badge' | 'disabled' | 'defaultOpen', Extract<keyof Row, string>>>;
154
154
  declare function mapAccordionFields<T>(records: readonly any[], fields: AccordionFieldMap | undefined): T[];
155
+ /**
156
+ * The appearance cascade, as rules every adapter and the docs share:
157
+ * 1. The variant decides what is a surface: the container (solid, outline), each row
158
+ * (separated, filled) or nothing (ghost).
159
+ * 2. `radius` shapes the container; `itemRadius` defaults to it.
160
+ * 3. A zero gap joins the rows. Joined separated or filled rows become one group whose outer
161
+ * corners follow the container `radius`; `dividers` draws the lines inside it.
162
+ */
163
+ type AccordionVariantName = 'solid' | 'outline' | 'separated' | 'filled' | 'ghost';
164
+ interface AccordionAppearanceInput {
165
+ variant?: AccordionVariantName;
166
+ /** Resolved gap: `dimensions.gap` if set, else `gap`. */
167
+ gap?: unknown;
168
+ radius?: unknown;
169
+ itemRadius?: unknown;
170
+ dividers?: boolean;
171
+ indicator?: unknown;
172
+ disabled?: boolean;
173
+ }
174
+ /** Whether the rows touch. The card and ghost variants default to no gap; separated and filled to a small one. */
175
+ declare function accordionRowsJoined(variant?: AccordionVariantName, gap?: unknown): boolean;
176
+ /**
177
+ * Options that have no visible effect with the rest of the config, each with the reason and
178
+ * what to do instead. Playgrounds disable them; the dev build warns when one is set explicitly.
179
+ */
180
+ declare function inactiveAccordionOptions(input: AccordionAppearanceInput): Record<string, string>;
155
181
  /** When panel content is mounted. */
156
182
  type AccordionMountPanels = 'always' | 'open' | 'first-open';
157
183
  /** Whether a panel's content should be in the DOM right now. */
@@ -179,7 +205,7 @@ declare function resetWarnings(): void;
179
205
  */
180
206
 
181
207
  /** Package version, injected at publish time by the release script. */
182
- declare const version = "0.1.0";
208
+ declare const version = "0.2.0";
183
209
 
184
- export { __DEV__, accordionFocusTarget, accordionPartIds, accordionRowId, ariaAttr, closeAllAccordion, cn, composeRefs, dataAttr, dimensionVars, initialAccordionValue, mapAccordionFields, mergeProps, nextIndexFromKey, normalizeAccordionValue, openAllAccordion, resetWarnings, resolveNode, setAccordionItem, shouldMountPanel, variants, version, warnOnce };
185
- export type { AccordionChangeDetails, AccordionChangeReason, AccordionFieldMap, AccordionKeyOptions, AccordionMode, AccordionMountPanels, AccordionRowLike, AccordionRules, Length, ListNavOptions, PinfoldDir, PinfoldLazy, PinfoldOrientation, PinfoldRadius, PinfoldSize, PinfoldStylePack, PinfoldTheme, PinfoldTone, PropsBag, Ref, Slottable, VariantProps };
210
+ export { __DEV__, accordionFocusTarget, accordionPartIds, accordionRowId, accordionRowsJoined, ariaAttr, closeAllAccordion, cn, composeRefs, dataAttr, dimensionVars, inactiveAccordionOptions, initialAccordionValue, mapAccordionFields, mergeProps, nextIndexFromKey, normalizeAccordionValue, openAllAccordion, resetWarnings, resolveNode, setAccordionItem, shouldMountPanel, variants, version, warnOnce };
211
+ export type { AccordionAppearanceInput, AccordionChangeDetails, AccordionChangeReason, AccordionFieldMap, AccordionKeyOptions, AccordionMode, AccordionMountPanels, AccordionRowLike, AccordionRules, AccordionVariantName, Length, ListNavOptions, PinfoldDir, PinfoldLazy, PinfoldOrientation, PinfoldRadius, PinfoldSize, PinfoldStylePack, PinfoldTheme, PinfoldTone, PropsBag, Ref, Slottable, VariantProps };
package/dist/index.d.ts CHANGED
@@ -8,7 +8,7 @@ type PinfoldTheme = 'light' | 'dark' | 'system';
8
8
  /** When a component's code loads. `auto`: light components eagerly, heavy ones when visible. */
9
9
  type PinfoldLazy = 'auto' | 'eager' | 'visible' | 'idle';
10
10
  /** Visual style pack, applied through `data-pinfold-style`. */
11
- type PinfoldStylePack = 'default' | 'glass' | 'bauhaus' | 'brutalist' | 'noise' | 'soft';
11
+ type PinfoldStylePack = 'default' | 'glass' | 'material' | 'noise' | 'brutalist';
12
12
  /** Any DOM props bag a component hands back from a `getXProps()` call. */
13
13
  type PropsBag = Record<string, any>;
14
14
  /** A CSS length. Numbers are px, strings pass through (`'50%'`, `'12rem'`, `calc()`). */
@@ -128,13 +128,13 @@ declare function initialAccordionValue(items: ReadonlyArray<{
128
128
  }>, defaultValue: readonly string[] | undefined, mode: AccordionMode): string[];
129
129
  /**
130
130
  * The open set after asking row `id` to become `open`, or `null` when the
131
- * rules refuse (unknown or disabled row, no change, or closing the only open
132
- * row of a non-collapsible single accordion).
131
+ * rules refuse (unknown or disabled row, no change, or closing the last open
132
+ * row of a non-collapsible accordion, in either mode).
133
133
  */
134
134
  declare function setAccordionItem(value: readonly string[], rows: readonly AccordionRowLike[], id: string, open: boolean, rules: AccordionRules): string[] | null;
135
135
  /** `openAll`: every enabled row, multiple mode only. */
136
136
  declare function openAllAccordion(value: readonly string[], rows: readonly AccordionRowLike[], rules: AccordionRules): string[] | null;
137
- /** `closeAll`: refused for a non-collapsible single accordion. Disabled rows keep their state. */
137
+ /** `closeAll`: refused for a non-collapsible accordion. Disabled rows keep their state. */
138
138
  declare function closeAllAccordion(value: readonly string[], rows: readonly AccordionRowLike[], rules: AccordionRules): string[] | null;
139
139
  interface AccordionKeyOptions {
140
140
  orientation?: PinfoldOrientation;
@@ -150,8 +150,34 @@ declare function accordionFocusTarget(key: string, index: number, count: number,
150
150
  * Field mapping: lets raw records (a CMS row, a DB result) feed the accordion
151
151
  * without a `.map()`. `{ id: 'slug', title: 'question', content: 'answer' }`.
152
152
  */
153
- type AccordionFieldMap = Partial<Record<'id' | 'title' | 'content' | 'description' | 'icon' | 'badge' | 'disabled' | 'defaultOpen', string>>;
153
+ type AccordionFieldMap<Row = any> = Partial<Record<'id' | 'title' | 'content' | 'description' | 'icon' | 'endIcon' | 'badge' | 'disabled' | 'defaultOpen', Extract<keyof Row, string>>>;
154
154
  declare function mapAccordionFields<T>(records: readonly any[], fields: AccordionFieldMap | undefined): T[];
155
+ /**
156
+ * The appearance cascade, as rules every adapter and the docs share:
157
+ * 1. The variant decides what is a surface: the container (solid, outline), each row
158
+ * (separated, filled) or nothing (ghost).
159
+ * 2. `radius` shapes the container; `itemRadius` defaults to it.
160
+ * 3. A zero gap joins the rows. Joined separated or filled rows become one group whose outer
161
+ * corners follow the container `radius`; `dividers` draws the lines inside it.
162
+ */
163
+ type AccordionVariantName = 'solid' | 'outline' | 'separated' | 'filled' | 'ghost';
164
+ interface AccordionAppearanceInput {
165
+ variant?: AccordionVariantName;
166
+ /** Resolved gap: `dimensions.gap` if set, else `gap`. */
167
+ gap?: unknown;
168
+ radius?: unknown;
169
+ itemRadius?: unknown;
170
+ dividers?: boolean;
171
+ indicator?: unknown;
172
+ disabled?: boolean;
173
+ }
174
+ /** Whether the rows touch. The card and ghost variants default to no gap; separated and filled to a small one. */
175
+ declare function accordionRowsJoined(variant?: AccordionVariantName, gap?: unknown): boolean;
176
+ /**
177
+ * Options that have no visible effect with the rest of the config, each with the reason and
178
+ * what to do instead. Playgrounds disable them; the dev build warns when one is set explicitly.
179
+ */
180
+ declare function inactiveAccordionOptions(input: AccordionAppearanceInput): Record<string, string>;
155
181
  /** When panel content is mounted. */
156
182
  type AccordionMountPanels = 'always' | 'open' | 'first-open';
157
183
  /** Whether a panel's content should be in the DOM right now. */
@@ -179,7 +205,7 @@ declare function resetWarnings(): void;
179
205
  */
180
206
 
181
207
  /** Package version, injected at publish time by the release script. */
182
- declare const version = "0.1.0";
208
+ declare const version = "0.2.0";
183
209
 
184
- export { __DEV__, accordionFocusTarget, accordionPartIds, accordionRowId, ariaAttr, closeAllAccordion, cn, composeRefs, dataAttr, dimensionVars, initialAccordionValue, mapAccordionFields, mergeProps, nextIndexFromKey, normalizeAccordionValue, openAllAccordion, resetWarnings, resolveNode, setAccordionItem, shouldMountPanel, variants, version, warnOnce };
185
- export type { AccordionChangeDetails, AccordionChangeReason, AccordionFieldMap, AccordionKeyOptions, AccordionMode, AccordionMountPanels, AccordionRowLike, AccordionRules, Length, ListNavOptions, PinfoldDir, PinfoldLazy, PinfoldOrientation, PinfoldRadius, PinfoldSize, PinfoldStylePack, PinfoldTheme, PinfoldTone, PropsBag, Ref, Slottable, VariantProps };
210
+ export { __DEV__, accordionFocusTarget, accordionPartIds, accordionRowId, accordionRowsJoined, ariaAttr, closeAllAccordion, cn, composeRefs, dataAttr, dimensionVars, inactiveAccordionOptions, initialAccordionValue, mapAccordionFields, mergeProps, nextIndexFromKey, normalizeAccordionValue, openAllAccordion, resetWarnings, resolveNode, setAccordionItem, shouldMountPanel, variants, version, warnOnce };
211
+ export type { AccordionAppearanceInput, AccordionChangeDetails, AccordionChangeReason, AccordionFieldMap, AccordionKeyOptions, AccordionMode, AccordionMountPanels, AccordionRowLike, AccordionRules, AccordionVariantName, Length, ListNavOptions, PinfoldDir, PinfoldLazy, PinfoldOrientation, PinfoldRadius, PinfoldSize, PinfoldStylePack, PinfoldTheme, PinfoldTone, PropsBag, Ref, Slottable, VariantProps };
package/dist/index.js CHANGED
@@ -128,7 +128,7 @@ function setAccordionItem(value, rows, id, open, rules) {
128
128
  if (!row || row.disabled || rules.disabled) return null;
129
129
  const isOpen = value.indexOf(id) !== -1;
130
130
  if (isOpen === open) return null;
131
- if (!open && rules.mode === "single" && !rules.collapsible) return null;
131
+ if (!open && !rules.collapsible && value.length === 1) return null;
132
132
  if (open) return rules.mode === "single" ? [id] : value.concat(id);
133
133
  return value.filter((v) => v !== id);
134
134
  }
@@ -138,7 +138,7 @@ function openAllAccordion(value, rows, rules) {
138
138
  return sameSet(value, next) ? null : next;
139
139
  }
140
140
  function closeAllAccordion(value, rows, rules) {
141
- if (rules.disabled || rules.mode === "single" && !rules.collapsible) return null;
141
+ if (rules.disabled || !rules.collapsible) return null;
142
142
  const locked = new Set(rows.filter((r) => r.disabled).map((r) => r.id));
143
143
  const next = value.filter((id) => locked.has(id));
144
144
  return sameSet(value, next) ? null : next;
@@ -160,6 +160,36 @@ function mapAccordionFields(records, fields) {
160
160
  return out;
161
161
  });
162
162
  }
163
+ var ZERO = /^0*\.?0*(px|rem|em|%|vh|vw)?$/;
164
+ function accordionRowsJoined(variant = "solid", gap) {
165
+ if (gap === void 0 || gap === null || gap === "") return variant !== "separated" && variant !== "filled";
166
+ if (gap === "none" || gap === 0) return true;
167
+ return typeof gap === "string" && ZERO.test(gap.trim());
168
+ }
169
+ function inactiveAccordionOptions(input) {
170
+ var _a;
171
+ const variant = (_a = input.variant) != null ? _a : "solid";
172
+ const joined = accordionRowsJoined(variant, input.gap);
173
+ const card = variant === "solid" || variant === "outline";
174
+ const rows = variant === "separated" || variant === "filled";
175
+ const out = {};
176
+ if (variant === "ghost") {
177
+ out.radius = "Ghost has no surface to round. Pick solid or outline for a rounded container.";
178
+ out.itemRadius = "Ghost rows have no surface to round. Pick separated or filled for rounded rows.";
179
+ }
180
+ if (card) out.itemRadius = "In a solid or outline card the rows are not surfaces; the card takes `radius`.";
181
+ if (rows && joined) out.itemRadius = "With no gap the rows join into one group, rounded by `radius`.";
182
+ if (rows && !joined && input.itemRadius !== void 0) {
183
+ out.radius = 'Separated and filled rows are the surfaces and `itemRadius` already rounds them. Unset `itemRadius` so `radius` reaches the rows, or set `gap: "none"` to join them.';
184
+ }
185
+ if (rows && !joined) out.dividers = 'The rows are already apart. Dividers appear when `gap` is "none".';
186
+ if (input.indicator === "none") {
187
+ out.indicatorPosition = 'There is no indicator (`indicator: "none"`).';
188
+ out.animateIndicator = 'There is no indicator (`indicator: "none"`).';
189
+ }
190
+ if (input.disabled) out.disabledIds = "Every row is already disabled (`disabled: true`).";
191
+ return out;
192
+ }
163
193
  function shouldMountPanel(policy, open, everOpened) {
164
194
  if (policy === "always" || open) return true;
165
195
  return policy === "first-open" && everOpened;
@@ -181,6 +211,6 @@ function resetWarnings() {
181
211
  }
182
212
 
183
213
  // src/index.ts
184
- var version = "0.1.0";
214
+ var version = "0.2.0";
185
215
 
186
- export { __DEV__, accordionFocusTarget, accordionPartIds, accordionRowId, ariaAttr, closeAllAccordion, cn, composeRefs, dataAttr, dimensionVars, initialAccordionValue, mapAccordionFields, mergeProps, nextIndexFromKey, normalizeAccordionValue, openAllAccordion, resetWarnings, resolveNode, setAccordionItem, shouldMountPanel, variants, version, warnOnce };
216
+ export { __DEV__, accordionFocusTarget, accordionPartIds, accordionRowId, accordionRowsJoined, ariaAttr, closeAllAccordion, cn, composeRefs, dataAttr, dimensionVars, inactiveAccordionOptions, initialAccordionValue, mapAccordionFields, mergeProps, nextIndexFromKey, normalizeAccordionValue, openAllAccordion, resetWarnings, resolveNode, setAccordionItem, shouldMountPanel, variants, version, warnOnce };
package/dist/meta.cjs CHANGED
@@ -4,6 +4,7 @@
4
4
  var SIZE = ["sm", "md", "lg"];
5
5
  var TONE = ["primary", "neutral", "success", "warning", "danger", "info"];
6
6
  var RADIUS = ["none", "sm", "md", "lg", "full"];
7
+ var GAP = ["none", "sm", "md", "lg"];
7
8
  var SLOTS = [
8
9
  "root",
9
10
  "item",
@@ -14,6 +15,7 @@ var SLOTS = [
14
15
  "title",
15
16
  "description",
16
17
  "badge",
18
+ "endIcon",
17
19
  "indicator",
18
20
  "panel",
19
21
  "content"
@@ -38,8 +40,9 @@ var item = {
38
40
  title: { ...node("The row heading. A node or `(state) => node`."), required: true },
39
41
  content: node("The panel body."),
40
42
  description: node("A secondary line under the title."),
41
- icon: node("A leading visual."),
43
+ icon: node("A leading visual: a node, or a string (an emoji, or an icon-font ligature with `classNames.icon`). Overrides the accordion-wide `icon`."),
42
44
  badge: node("A trailing chip before the indicator."),
45
+ endIcon: node("A trailing visual just before the indicator. Overrides the accordion-wide `endIcon`."),
43
46
  indicator: {
44
47
  type: "enum",
45
48
  values: ["chevron", "plus", "caret", "arrow", "none"],
@@ -72,7 +75,7 @@ var accordionMeta = {
72
75
  description: "Map raw records onto rows: `{ id: 'slug', title: 'question', content: 'answer' }`.",
73
76
  group: "data",
74
77
  keys: Object.fromEntries(
75
- ["id", "title", "content", "description", "icon", "badge", "disabled", "defaultOpen"].map((k) => [
78
+ ["id", "title", "content", "description", "icon", "endIcon", "badge", "disabled", "defaultOpen"].map((k) => [
76
79
  k,
77
80
  { type: "string", description: `Source field for \`${k}\`.`, group: "data" }
78
81
  ])
@@ -89,7 +92,7 @@ var accordionMeta = {
89
92
  collapsible: {
90
93
  type: "boolean",
91
94
  default: true,
92
- description: "In single mode, lets the user close the open row.",
95
+ description: "Lets the user close the last open row. `false` keeps at least one row open, in either mode.",
93
96
  group: "behaviour",
94
97
  control: true
95
98
  },
@@ -102,6 +105,7 @@ var accordionMeta = {
102
105
  group: "behaviour",
103
106
  control: true
104
107
  },
108
+ disabledIds: { type: "string[]", description: "Disables these rows by id.", group: "behaviour" },
105
109
  orientation: {
106
110
  type: "enum",
107
111
  values: ["vertical", "horizontal"],
@@ -155,7 +159,35 @@ var accordionMeta = {
155
159
  },
156
160
  size: { type: "enum", values: SIZE, default: "md", description: "Size preset.", group: "appearance", control: true },
157
161
  tone: { type: "enum", values: TONE, default: "primary", description: "Accent colour role.", group: "appearance", control: true },
158
- radius: { type: "enum", values: RADIUS, default: "md", description: "Corner radius preset.", group: "appearance", control: true },
162
+ radius: {
163
+ type: "enum",
164
+ values: RADIUS,
165
+ orLength: true,
166
+ auto: "the theme radius (provider `radius`, default md)",
167
+ description: "Corner radius of the container that holds the rows: a preset, or a length (`20` is px, `'1.25rem'` passes through). Default: the theme radius.",
168
+ group: "appearance",
169
+ control: true
170
+ },
171
+ itemRadius: {
172
+ type: "enum",
173
+ values: RADIUS,
174
+ orLength: true,
175
+ auto: "follows `radius`",
176
+ description: "Corner radius of each row, same values as `radius`. Default: `radius` for separated, filled and ghost rows; square rows inside a solid or outline card.",
177
+ group: "appearance",
178
+ control: true
179
+ },
180
+ gap: {
181
+ type: "enum",
182
+ values: GAP,
183
+ orLength: true,
184
+ auto: "none; sm for separated and filled",
185
+ description: "Space between rows: a preset, or a length (`12` is px). Default: none for solid, outline and ghost; `'sm'` for separated and filled.",
186
+ group: "appearance",
187
+ control: true
188
+ },
189
+ icon: { ...node("Leading visual for every row: a node, or `(state) => node`. A row\u2019s own `icon` wins."), group: "appearance" },
190
+ endIcon: { ...node("Trailing visual for every row, just before the indicator. A row\u2019s own `endIcon` wins."), group: "appearance" },
159
191
  indicator: {
160
192
  type: "enum",
161
193
  values: ["chevron", "plus", "caret", "arrow", "none"],
@@ -173,6 +205,13 @@ var accordionMeta = {
173
205
  group: "appearance",
174
206
  control: true
175
207
  },
208
+ animateIndicator: {
209
+ type: "boolean",
210
+ default: true,
211
+ description: "Rotate (or morph) the indicator when a row opens.",
212
+ group: "appearance",
213
+ control: true
214
+ },
176
215
  headingLevel: {
177
216
  type: "enum",
178
217
  values: [1, 2, 3, 4, 5, 6],
@@ -187,7 +226,7 @@ var accordionMeta = {
187
226
  group: "appearance",
188
227
  control: true
189
228
  },
190
- dividers: { type: "boolean", default: true, description: "Lines between rows.", group: "appearance", control: true },
229
+ dividers: { type: "boolean", default: true, description: "Lines between rows (solid, outline and ghost).", group: "appearance", control: true },
191
230
  overflow: {
192
231
  type: "object",
193
232
  description: "Text overflow per slot. Default: title wraps, description clamps to 2 lines.",
@@ -204,7 +243,7 @@ var accordionMeta = {
204
243
  maxWidth: len("Maximum width."),
205
244
  height: len("Fixed height."),
206
245
  maxHeight: len("Caps the root; rows scroll."),
207
- gap: len("Space between rows."),
246
+ gap: len("Space between rows. Overrides `gap`."),
208
247
  paddingX: len("Trigger horizontal padding."),
209
248
  paddingY: len("Trigger vertical padding."),
210
249
  panelPaddingX: len("Panel horizontal padding."),
@@ -216,7 +255,8 @@ var accordionMeta = {
216
255
  iconSize: len("Icon box size."),
217
256
  indicatorSize: len("Indicator size."),
218
257
  borderWidth: len("Border width."),
219
- radius: len("Corner radius as a length."),
258
+ radius: len("Container corner radius. Overrides `radius`."),
259
+ itemRadius: len("Row corner radius. Overrides `itemRadius`."),
220
260
  duration: len("Transition duration, e.g. '320ms'.")
221
261
  }
222
262
  },
@@ -280,6 +320,8 @@ var accordionMeta = {
280
320
  "--pinfold-accordion-panel-max-height",
281
321
  "--pinfold-accordion-gap",
282
322
  "--pinfold-accordion-radius",
323
+ "--pinfold-accordion-item-radius",
324
+ "--pinfold-accordion-divider-color",
283
325
  "--pinfold-accordion-duration"
284
326
  ],
285
327
  examples: [
@@ -361,7 +403,8 @@ function checkType(prop, v) {
361
403
  case "enum":
362
404
  if (prop.values.indexOf(v) !== -1) return null;
363
405
  if (prop.orNode && typeof v !== "string") return null;
364
- return `must be one of ${list}, got ${JSON.stringify(v)}.`;
406
+ if (prop.orLength && (typeof v === "number" || typeof v === "string" && /\d|\(/.test(v))) return null;
407
+ return `must be one of ${list}${prop.orLength ? " or a length" : ""}, got ${JSON.stringify(v)}.`;
365
408
  case "node":
366
409
  return null;
367
410
  }
@@ -423,6 +466,7 @@ function toJsonSchema(meta) {
423
466
  case "string[]":
424
467
  return { ...base, type: "array", items: { type: "string" } };
425
468
  case "enum":
469
+ if (p.orLength) return { ...base, anyOf: [{ enum: p.values }, { type: ["number", "string"] }] };
426
470
  return p.orNode ? { ...base, anyOf: [{ enum: p.values }, {}] } : { ...base, enum: p.values };
427
471
  case "array":
428
472
  return { ...base, type: "array", items: p.items ? { ...shape(p.items), additionalProperties: true } : {} };
@@ -445,12 +489,12 @@ function toJsonSchema(meta) {
445
489
  function metaToMarkdown(meta) {
446
490
  var _a;
447
491
  const cell = (s) => String(s != null ? s : "").replace(/\|/g, "\\|").replace(/\n/g, " ");
448
- const type = (p) => p.type === "enum" ? p.values.map((v) => JSON.stringify(v)).join(" \\| ") + (p.orNode ? " \\| node" : "") : p.type;
492
+ const type = (p) => p.type === "enum" ? p.values.map((v) => JSON.stringify(v)).join(" \\| ") + (p.orNode ? " \\| node" : "") + (p.orLength ? " \\| length" : "") : p.type;
449
493
  const table = (props) => [
450
494
  "| Option | Type | Default | Description |",
451
495
  "|---|---|---|---|",
452
496
  ...Object.entries(props).map(
453
- ([k, p]) => `| \`${k}\`${p.required ? " (required)" : ""}${p.status === "deprecated" ? " (deprecated)" : ""} | ${type(p)} | ${p.default === void 0 ? "" : `\`${JSON.stringify(p.default)}\``} | ${cell(p.description)} |`
497
+ ([k, p]) => `| \`${k}\`${p.required ? " (required)" : ""}${p.status === "deprecated" ? " (deprecated)" : ""} | ${type(p)} | ${p.default === void 0 ? p.auto ? `auto: ${p.auto}` : "" : `\`${JSON.stringify(p.default)}\``} | ${cell(p.description)} |`
454
498
  )
455
499
  ].join("\n");
456
500
  const rows = (_a = meta.props.items) == null ? void 0 : _a.items;
package/dist/meta.d.cts CHANGED
@@ -22,7 +22,11 @@ interface PropMeta {
22
22
  values?: readonly (string | number)[];
23
23
  /** Also accepts a node, e.g. `indicator`: a glyph name or your own node. */
24
24
  orNode?: boolean;
25
+ /** Also accepts a length, e.g. `radius`: a preset name or `20` / `'1.25rem'`. */
26
+ orLength?: boolean;
25
27
  default?: unknown;
28
+ /** What an unset option resolves to when it has no fixed default (e.g. `theme radius`). */
29
+ auto?: string;
26
30
  required?: boolean;
27
31
  description: string;
28
32
  group: PropGroup;
package/dist/meta.d.ts CHANGED
@@ -22,7 +22,11 @@ interface PropMeta {
22
22
  values?: readonly (string | number)[];
23
23
  /** Also accepts a node, e.g. `indicator`: a glyph name or your own node. */
24
24
  orNode?: boolean;
25
+ /** Also accepts a length, e.g. `radius`: a preset name or `20` / `'1.25rem'`. */
26
+ orLength?: boolean;
25
27
  default?: unknown;
28
+ /** What an unset option resolves to when it has no fixed default (e.g. `theme radius`). */
29
+ auto?: string;
26
30
  required?: boolean;
27
31
  description: string;
28
32
  group: PropGroup;
package/dist/meta.js CHANGED
@@ -2,6 +2,7 @@
2
2
  var SIZE = ["sm", "md", "lg"];
3
3
  var TONE = ["primary", "neutral", "success", "warning", "danger", "info"];
4
4
  var RADIUS = ["none", "sm", "md", "lg", "full"];
5
+ var GAP = ["none", "sm", "md", "lg"];
5
6
  var SLOTS = [
6
7
  "root",
7
8
  "item",
@@ -12,6 +13,7 @@ var SLOTS = [
12
13
  "title",
13
14
  "description",
14
15
  "badge",
16
+ "endIcon",
15
17
  "indicator",
16
18
  "panel",
17
19
  "content"
@@ -36,8 +38,9 @@ var item = {
36
38
  title: { ...node("The row heading. A node or `(state) => node`."), required: true },
37
39
  content: node("The panel body."),
38
40
  description: node("A secondary line under the title."),
39
- icon: node("A leading visual."),
41
+ icon: node("A leading visual: a node, or a string (an emoji, or an icon-font ligature with `classNames.icon`). Overrides the accordion-wide `icon`."),
40
42
  badge: node("A trailing chip before the indicator."),
43
+ endIcon: node("A trailing visual just before the indicator. Overrides the accordion-wide `endIcon`."),
41
44
  indicator: {
42
45
  type: "enum",
43
46
  values: ["chevron", "plus", "caret", "arrow", "none"],
@@ -70,7 +73,7 @@ var accordionMeta = {
70
73
  description: "Map raw records onto rows: `{ id: 'slug', title: 'question', content: 'answer' }`.",
71
74
  group: "data",
72
75
  keys: Object.fromEntries(
73
- ["id", "title", "content", "description", "icon", "badge", "disabled", "defaultOpen"].map((k) => [
76
+ ["id", "title", "content", "description", "icon", "endIcon", "badge", "disabled", "defaultOpen"].map((k) => [
74
77
  k,
75
78
  { type: "string", description: `Source field for \`${k}\`.`, group: "data" }
76
79
  ])
@@ -87,7 +90,7 @@ var accordionMeta = {
87
90
  collapsible: {
88
91
  type: "boolean",
89
92
  default: true,
90
- description: "In single mode, lets the user close the open row.",
93
+ description: "Lets the user close the last open row. `false` keeps at least one row open, in either mode.",
91
94
  group: "behaviour",
92
95
  control: true
93
96
  },
@@ -100,6 +103,7 @@ var accordionMeta = {
100
103
  group: "behaviour",
101
104
  control: true
102
105
  },
106
+ disabledIds: { type: "string[]", description: "Disables these rows by id.", group: "behaviour" },
103
107
  orientation: {
104
108
  type: "enum",
105
109
  values: ["vertical", "horizontal"],
@@ -153,7 +157,35 @@ var accordionMeta = {
153
157
  },
154
158
  size: { type: "enum", values: SIZE, default: "md", description: "Size preset.", group: "appearance", control: true },
155
159
  tone: { type: "enum", values: TONE, default: "primary", description: "Accent colour role.", group: "appearance", control: true },
156
- radius: { type: "enum", values: RADIUS, default: "md", description: "Corner radius preset.", group: "appearance", control: true },
160
+ radius: {
161
+ type: "enum",
162
+ values: RADIUS,
163
+ orLength: true,
164
+ auto: "the theme radius (provider `radius`, default md)",
165
+ description: "Corner radius of the container that holds the rows: a preset, or a length (`20` is px, `'1.25rem'` passes through). Default: the theme radius.",
166
+ group: "appearance",
167
+ control: true
168
+ },
169
+ itemRadius: {
170
+ type: "enum",
171
+ values: RADIUS,
172
+ orLength: true,
173
+ auto: "follows `radius`",
174
+ description: "Corner radius of each row, same values as `radius`. Default: `radius` for separated, filled and ghost rows; square rows inside a solid or outline card.",
175
+ group: "appearance",
176
+ control: true
177
+ },
178
+ gap: {
179
+ type: "enum",
180
+ values: GAP,
181
+ orLength: true,
182
+ auto: "none; sm for separated and filled",
183
+ description: "Space between rows: a preset, or a length (`12` is px). Default: none for solid, outline and ghost; `'sm'` for separated and filled.",
184
+ group: "appearance",
185
+ control: true
186
+ },
187
+ icon: { ...node("Leading visual for every row: a node, or `(state) => node`. A row\u2019s own `icon` wins."), group: "appearance" },
188
+ endIcon: { ...node("Trailing visual for every row, just before the indicator. A row\u2019s own `endIcon` wins."), group: "appearance" },
157
189
  indicator: {
158
190
  type: "enum",
159
191
  values: ["chevron", "plus", "caret", "arrow", "none"],
@@ -171,6 +203,13 @@ var accordionMeta = {
171
203
  group: "appearance",
172
204
  control: true
173
205
  },
206
+ animateIndicator: {
207
+ type: "boolean",
208
+ default: true,
209
+ description: "Rotate (or morph) the indicator when a row opens.",
210
+ group: "appearance",
211
+ control: true
212
+ },
174
213
  headingLevel: {
175
214
  type: "enum",
176
215
  values: [1, 2, 3, 4, 5, 6],
@@ -185,7 +224,7 @@ var accordionMeta = {
185
224
  group: "appearance",
186
225
  control: true
187
226
  },
188
- dividers: { type: "boolean", default: true, description: "Lines between rows.", group: "appearance", control: true },
227
+ dividers: { type: "boolean", default: true, description: "Lines between rows (solid, outline and ghost).", group: "appearance", control: true },
189
228
  overflow: {
190
229
  type: "object",
191
230
  description: "Text overflow per slot. Default: title wraps, description clamps to 2 lines.",
@@ -202,7 +241,7 @@ var accordionMeta = {
202
241
  maxWidth: len("Maximum width."),
203
242
  height: len("Fixed height."),
204
243
  maxHeight: len("Caps the root; rows scroll."),
205
- gap: len("Space between rows."),
244
+ gap: len("Space between rows. Overrides `gap`."),
206
245
  paddingX: len("Trigger horizontal padding."),
207
246
  paddingY: len("Trigger vertical padding."),
208
247
  panelPaddingX: len("Panel horizontal padding."),
@@ -214,7 +253,8 @@ var accordionMeta = {
214
253
  iconSize: len("Icon box size."),
215
254
  indicatorSize: len("Indicator size."),
216
255
  borderWidth: len("Border width."),
217
- radius: len("Corner radius as a length."),
256
+ radius: len("Container corner radius. Overrides `radius`."),
257
+ itemRadius: len("Row corner radius. Overrides `itemRadius`."),
218
258
  duration: len("Transition duration, e.g. '320ms'.")
219
259
  }
220
260
  },
@@ -278,6 +318,8 @@ var accordionMeta = {
278
318
  "--pinfold-accordion-panel-max-height",
279
319
  "--pinfold-accordion-gap",
280
320
  "--pinfold-accordion-radius",
321
+ "--pinfold-accordion-item-radius",
322
+ "--pinfold-accordion-divider-color",
281
323
  "--pinfold-accordion-duration"
282
324
  ],
283
325
  examples: [
@@ -359,7 +401,8 @@ function checkType(prop, v) {
359
401
  case "enum":
360
402
  if (prop.values.indexOf(v) !== -1) return null;
361
403
  if (prop.orNode && typeof v !== "string") return null;
362
- return `must be one of ${list}, got ${JSON.stringify(v)}.`;
404
+ if (prop.orLength && (typeof v === "number" || typeof v === "string" && /\d|\(/.test(v))) return null;
405
+ return `must be one of ${list}${prop.orLength ? " or a length" : ""}, got ${JSON.stringify(v)}.`;
363
406
  case "node":
364
407
  return null;
365
408
  }
@@ -421,6 +464,7 @@ function toJsonSchema(meta) {
421
464
  case "string[]":
422
465
  return { ...base, type: "array", items: { type: "string" } };
423
466
  case "enum":
467
+ if (p.orLength) return { ...base, anyOf: [{ enum: p.values }, { type: ["number", "string"] }] };
424
468
  return p.orNode ? { ...base, anyOf: [{ enum: p.values }, {}] } : { ...base, enum: p.values };
425
469
  case "array":
426
470
  return { ...base, type: "array", items: p.items ? { ...shape(p.items), additionalProperties: true } : {} };
@@ -443,12 +487,12 @@ function toJsonSchema(meta) {
443
487
  function metaToMarkdown(meta) {
444
488
  var _a;
445
489
  const cell = (s) => String(s != null ? s : "").replace(/\|/g, "\\|").replace(/\n/g, " ");
446
- const type = (p) => p.type === "enum" ? p.values.map((v) => JSON.stringify(v)).join(" \\| ") + (p.orNode ? " \\| node" : "") : p.type;
490
+ const type = (p) => p.type === "enum" ? p.values.map((v) => JSON.stringify(v)).join(" \\| ") + (p.orNode ? " \\| node" : "") + (p.orLength ? " \\| length" : "") : p.type;
447
491
  const table = (props) => [
448
492
  "| Option | Type | Default | Description |",
449
493
  "|---|---|---|---|",
450
494
  ...Object.entries(props).map(
451
- ([k, p]) => `| \`${k}\`${p.required ? " (required)" : ""}${p.status === "deprecated" ? " (deprecated)" : ""} | ${type(p)} | ${p.default === void 0 ? "" : `\`${JSON.stringify(p.default)}\``} | ${cell(p.description)} |`
495
+ ([k, p]) => `| \`${k}\`${p.required ? " (required)" : ""}${p.status === "deprecated" ? " (deprecated)" : ""} | ${type(p)} | ${p.default === void 0 ? p.auto ? `auto: ${p.auto}` : "" : `\`${JSON.stringify(p.default)}\``} | ${cell(p.description)} |`
452
496
  )
453
497
  ].join("\n");
454
498
  const rows = (_a = meta.props.items) == null ? void 0 : _a.items;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pinfold/core",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Pinfold core: framework-free logic, palette engine and helpers shared by every Pinfold adapter.",
5
5
  "author": "Indranil Bhattacharya",
6
6
  "keywords": [