bsmnt 0.12.0 → 0.13.1

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.
Files changed (188) hide show
  1. package/dist/application/add-integration/index.d.ts.map +1 -1
  2. package/dist/application/add-integration/index.js +23 -0
  3. package/dist/application/add-integration/index.js.map +1 -1
  4. package/dist/domain/consent.d.ts +17 -0
  5. package/dist/domain/consent.d.ts.map +1 -0
  6. package/dist/domain/consent.js +13 -0
  7. package/dist/domain/consent.js.map +1 -0
  8. package/dist/domain/integration.d.ts +19 -2
  9. package/dist/domain/integration.d.ts.map +1 -1
  10. package/dist/domain/integration.js +10 -1
  11. package/dist/domain/integration.js.map +1 -1
  12. package/dist/domain/jobs.d.ts +17 -0
  13. package/dist/domain/jobs.d.ts.map +1 -0
  14. package/dist/domain/jobs.js +13 -0
  15. package/dist/domain/jobs.js.map +1 -0
  16. package/dist/domain/skills.d.ts +23 -2
  17. package/dist/domain/skills.d.ts.map +1 -1
  18. package/dist/domain/skills.js +23 -2
  19. package/dist/domain/skills.js.map +1 -1
  20. package/dist/domain/templates.d.ts +7 -1
  21. package/dist/domain/templates.d.ts.map +1 -1
  22. package/dist/domain/templates.js +2 -0
  23. package/dist/domain/templates.js.map +1 -1
  24. package/dist/index.js +34 -9
  25. package/dist/index.js.map +1 -1
  26. package/dist/infrastructure/create/setup-agent.d.ts.map +1 -1
  27. package/dist/infrastructure/create/setup-agent.js +11 -4
  28. package/dist/infrastructure/create/setup-agent.js.map +1 -1
  29. package/dist/infrastructure/integrations/inject-integration.d.ts.map +1 -1
  30. package/dist/infrastructure/integrations/inject-integration.js +36 -2
  31. package/dist/infrastructure/integrations/inject-integration.js.map +1 -1
  32. package/dist/modules/features/consent/ketch/config.d.ts +18 -0
  33. package/dist/modules/features/consent/ketch/config.d.ts.map +1 -0
  34. package/dist/modules/features/consent/ketch/config.js +24 -0
  35. package/dist/modules/features/consent/ketch/config.js.map +1 -0
  36. package/dist/modules/features/consent/ketch/mergers/biome.d.ts +16 -0
  37. package/dist/modules/features/consent/ketch/mergers/biome.d.ts.map +1 -0
  38. package/dist/modules/features/consent/ketch/mergers/biome.js +31 -0
  39. package/dist/modules/features/consent/ketch/mergers/biome.js.map +1 -0
  40. package/dist/modules/features/consent/ketch/mergers/check-integration.d.ts +13 -0
  41. package/dist/modules/features/consent/ketch/mergers/check-integration.d.ts.map +1 -0
  42. package/dist/modules/features/consent/ketch/mergers/check-integration.js +64 -0
  43. package/dist/modules/features/consent/ketch/mergers/check-integration.js.map +1 -0
  44. package/dist/modules/features/consent/ketch/mergers/env-example.d.ts +12 -0
  45. package/dist/modules/features/consent/ketch/mergers/env-example.d.ts.map +1 -0
  46. package/dist/modules/features/consent/ketch/mergers/env-example.js +28 -0
  47. package/dist/modules/features/consent/ketch/mergers/env-example.js.map +1 -0
  48. package/dist/modules/features/consent/ketch/mergers/footer.d.ts +17 -0
  49. package/dist/modules/features/consent/ketch/mergers/footer.d.ts.map +1 -0
  50. package/dist/modules/features/consent/ketch/mergers/footer.js +40 -0
  51. package/dist/modules/features/consent/ketch/mergers/footer.js.map +1 -0
  52. package/dist/modules/features/consent/ketch/mergers/knip.d.ts +18 -0
  53. package/dist/modules/features/consent/ketch/mergers/knip.d.ts.map +1 -0
  54. package/dist/modules/features/consent/ketch/mergers/knip.js +37 -0
  55. package/dist/modules/features/consent/ketch/mergers/knip.js.map +1 -0
  56. package/dist/modules/features/consent/ketch/mergers/layout.d.ts +25 -0
  57. package/dist/modules/features/consent/ketch/mergers/layout.d.ts.map +1 -0
  58. package/dist/modules/features/consent/ketch/mergers/layout.js +55 -0
  59. package/dist/modules/features/consent/ketch/mergers/layout.js.map +1 -0
  60. package/dist/modules/features/jobs/ashby/config.d.ts +19 -0
  61. package/dist/modules/features/jobs/ashby/config.d.ts.map +1 -0
  62. package/dist/modules/features/jobs/ashby/config.js +29 -0
  63. package/dist/modules/features/jobs/ashby/config.js.map +1 -0
  64. package/dist/modules/features/jobs/ashby/mergers/check-integration.d.ts +14 -0
  65. package/dist/modules/features/jobs/ashby/mergers/check-integration.d.ts.map +1 -0
  66. package/dist/modules/features/jobs/ashby/mergers/check-integration.js +78 -0
  67. package/dist/modules/features/jobs/ashby/mergers/check-integration.js.map +1 -0
  68. package/dist/modules/features/jobs/ashby/mergers/env-example.d.ts +12 -0
  69. package/dist/modules/features/jobs/ashby/mergers/env-example.d.ts.map +1 -0
  70. package/dist/modules/features/jobs/ashby/mergers/env-example.js +27 -0
  71. package/dist/modules/features/jobs/ashby/mergers/env-example.js.map +1 -0
  72. package/dist/modules/features/jobs/ashby/mergers/knip.d.ts +18 -0
  73. package/dist/modules/features/jobs/ashby/mergers/knip.d.ts.map +1 -0
  74. package/dist/modules/features/jobs/ashby/mergers/knip.js +41 -0
  75. package/dist/modules/features/jobs/ashby/mergers/knip.js.map +1 -0
  76. package/dist/paths.d.ts +12 -0
  77. package/dist/paths.d.ts.map +1 -1
  78. package/dist/paths.js +12 -0
  79. package/dist/paths.js.map +1 -1
  80. package/package.json +3 -1
  81. package/src/agent-skills/ashby-pagebuilder-block/SKILL.md +277 -0
  82. package/src/agent-skills/hubspot-pagebuilder-block/SKILL.md +58 -25
  83. package/src/agent-skills/ketch-consent-check/SKILL.md +197 -0
  84. package/src/agent-skills/ketch-consent-setup/SKILL.md +326 -0
  85. package/src/agent-skills/ketch-consent-setup/references/ui-customization.md +329 -0
  86. package/src/agent-skills/page-builder-block/SKILL.md +150 -0
  87. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/components/anchor-id-input.tsx +17 -0
  88. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/components/presentation-input.tsx +146 -0
  89. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/components/spacing-steps-input.tsx +84 -0
  90. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/fetchers/layout.ts +6 -3
  91. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/icons.ts +8 -0
  92. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/page-builder-config.ts +1 -1
  93. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/presentation.ts +47 -15
  94. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/queries.ts +12 -0
  95. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/components/reusable/blog-content.ts +5 -5
  96. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/components/reusable/description.ts +3 -3
  97. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/components/reusable/gated-content.ts +3 -3
  98. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/components/reusable/hero.ts +3 -3
  99. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/components/reusable/rich-text-block.ts +3 -3
  100. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/components/singleton/content-collection.ts +3 -3
  101. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/index.ts +8 -0
  102. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/layout/spacing-settings.ts +120 -0
  103. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/shared/anchor.ts +75 -0
  104. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/shared/block.ts +46 -0
  105. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/shared/presentation.ts +44 -0
  106. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/shared/spacing.ts +164 -0
  107. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/singletons.ts +7 -0
  108. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/structure.ts +4 -4
  109. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/components/anchor-id-input.tsx +17 -0
  110. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/components/presentation-input.tsx +146 -0
  111. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/components/spacing-steps-input.tsx +84 -0
  112. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/fetchers/layout.ts +4 -2
  113. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/icons.ts +8 -0
  114. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/page-builder-config.ts +1 -1
  115. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/presentation.ts +47 -15
  116. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/queries.ts +12 -0
  117. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/components/reusable/blog-content.ts +5 -5
  118. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/components/reusable/description.ts +3 -3
  119. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/components/reusable/gated-content.ts +3 -3
  120. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/components/reusable/hero.ts +3 -3
  121. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/components/reusable/rich-text-block.ts +3 -3
  122. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/components/singleton/content-collection.ts +3 -3
  123. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/index.ts +8 -0
  124. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/layout/spacing-settings.ts +120 -0
  125. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/shared/anchor.ts +75 -0
  126. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/shared/block.ts +46 -0
  127. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/shared/presentation.ts +44 -0
  128. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/shared/spacing.ts +164 -0
  129. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/singletons.ts +7 -0
  130. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/structure.ts +4 -4
  131. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/tags.ts +2 -0
  132. package/src/modules/features/consent/ketch/files/lib/integrations/check-integration.ts +25 -0
  133. package/src/modules/features/consent/ketch/files/lib/integrations/ketch/README.md +70 -0
  134. package/src/modules/features/consent/ketch/files/lib/integrations/ketch/categories.ts +20 -0
  135. package/src/modules/features/consent/ketch/files/lib/integrations/ketch/consent-store.ts +107 -0
  136. package/src/modules/features/consent/ketch/files/lib/integrations/ketch/env.ts +20 -0
  137. package/src/modules/features/consent/ketch/files/lib/integrations/ketch/ketch-experience.css +136 -0
  138. package/src/modules/features/consent/ketch/files/lib/integrations/ketch/preferences-button.tsx +51 -0
  139. package/src/modules/features/consent/ketch/files/lib/integrations/ketch/script.tsx +8 -0
  140. package/src/modules/features/consent/ketch/files/lib/integrations/ketch/types.ts +15 -0
  141. package/src/modules/features/consent/ketch/files/lib/integrations/ketch/use-consent.ts +26 -0
  142. package/src/modules/features/jobs/ashby/files/_sanity-adapter/job-board-block.ts +53 -0
  143. package/src/modules/features/jobs/ashby/files/lib/integrations/ashby/README.md +134 -0
  144. package/src/modules/features/jobs/ashby/files/lib/integrations/ashby/env.ts +3 -0
  145. package/src/modules/features/jobs/ashby/files/lib/integrations/ashby/index.ts +40 -0
  146. package/src/modules/features/jobs/ashby/files/lib/integrations/ashby/normalize.ts +124 -0
  147. package/src/modules/features/jobs/ashby/files/lib/integrations/ashby/types.ts +20 -0
  148. package/src/modules/features/jobs/ashby/files/lib/integrations/check-integration.ts +25 -0
  149. package/src/templates/next-default/lib/utils/README.md +1 -0
  150. package/src/templates/next-default/lib/utils/image-sizes.ts +64 -25
  151. package/src/templates/next-experiments/lib/utils/README.md +1 -0
  152. package/src/templates/next-experiments/lib/utils/image-sizes.ts +64 -25
  153. package/src/templates/next-pagebuilder/AGENTS.md +129 -0
  154. package/src/templates/next-pagebuilder/CLAUDE.md +3 -0
  155. package/src/templates/next-pagebuilder/README.md +70 -0
  156. package/src/templates/next-pagebuilder/app/(content)/layout.tsx +2 -0
  157. package/src/templates/next-pagebuilder/components/layout/spacing-scale/index.tsx +18 -0
  158. package/src/templates/next-pagebuilder/components/page-builder/components/article-content/index.tsx +1 -1
  159. package/src/templates/next-pagebuilder/components/page-builder/components/content-collection/index.tsx +2 -2
  160. package/src/templates/next-pagebuilder/components/page-builder/components/description.tsx +3 -7
  161. package/src/templates/next-pagebuilder/components/page-builder/components/gated-content/index.tsx +2 -2
  162. package/src/templates/next-pagebuilder/components/page-builder/components/hero.tsx +3 -7
  163. package/src/templates/next-pagebuilder/components/page-builder/components/rich-text-block.tsx +3 -5
  164. package/src/templates/next-pagebuilder/components/page-builder/registry.tsx +70 -0
  165. package/src/templates/next-pagebuilder/components/page-builder/renderer.tsx +33 -49
  166. package/src/templates/next-pagebuilder/components/page-builder/section.tsx +35 -0
  167. package/src/templates/next-pagebuilder/components/page-document/index.tsx +2 -2
  168. package/src/templates/next-pagebuilder/lib/integrations/sanity/sanity.types.ts +92 -4
  169. package/src/templates/next-pagebuilder/lib/utils/image-sizes.ts +64 -25
  170. package/src/templates/next-pagebuilder-cache/AGENTS.md +136 -0
  171. package/src/templates/next-pagebuilder-cache/CLAUDE.md +3 -0
  172. package/src/templates/next-pagebuilder-cache/README.md +75 -0
  173. package/src/templates/next-pagebuilder-cache/app/(content)/layout.tsx +2 -0
  174. package/src/templates/next-pagebuilder-cache/components/layout/spacing-scale/index.tsx +46 -0
  175. package/src/templates/next-pagebuilder-cache/components/page-builder/components/article-content/index.tsx +1 -1
  176. package/src/templates/next-pagebuilder-cache/components/page-builder/components/content-collection/index.tsx +2 -2
  177. package/src/templates/next-pagebuilder-cache/components/page-builder/components/description.tsx +3 -7
  178. package/src/templates/next-pagebuilder-cache/components/page-builder/components/gated-content/index.tsx +2 -2
  179. package/src/templates/next-pagebuilder-cache/components/page-builder/components/hero.tsx +3 -7
  180. package/src/templates/next-pagebuilder-cache/components/page-builder/components/rich-text-block.tsx +3 -5
  181. package/src/templates/next-pagebuilder-cache/components/page-builder/registry.tsx +70 -0
  182. package/src/templates/next-pagebuilder-cache/components/page-builder/renderer.tsx +33 -49
  183. package/src/templates/next-pagebuilder-cache/components/page-builder/section.tsx +35 -0
  184. package/src/templates/next-pagebuilder-cache/components/page-document/index.tsx +2 -2
  185. package/src/templates/next-pagebuilder-cache/lib/integrations/sanity/sanity.types.ts +92 -4
  186. package/src/templates/next-pagebuilder-cache/lib/utils/image-sizes.ts +64 -25
  187. package/src/templates/next-webgl/lib/utils/README.md +1 -0
  188. package/src/templates/next-webgl/lib/utils/image-sizes.ts +64 -25
@@ -15,40 +15,55 @@ export type ByBreakpoint<
15
15
  B extends BreakpointMap = typeof BASEMENT_BREAKPOINTS,
16
16
  > = { base: T } & Partial<Record<Extract<keyof B, string>, T>>
17
17
 
18
+ export type ImageSizesOptions<
19
+ B extends BreakpointMap = typeof BASEMENT_BREAKPOINTS,
20
+ > = { breakpoints?: B }
21
+
22
+ export type GridImageSizesOptions<
23
+ B extends BreakpointMap = typeof BASEMENT_BREAKPOINTS,
24
+ > = ImageSizesOptions<B> & {
25
+ /** The grid container's `max-width` in px, past which columns stop growing. */
26
+ maxWidth?: number
27
+ }
28
+
29
+ type Condition<T> = { min: number; value: T }
30
+
18
31
  function columnWidth(columns: number): string {
19
32
  return `${Math.round(100 / columns)}vw`
20
33
  }
21
34
 
35
+ function toConditions<T>(
36
+ overrides: Readonly<Record<string, T | undefined>>,
37
+ scale: BreakpointMap
38
+ ): Condition<T>[] {
39
+ return Object.entries(overrides).flatMap(([name, value]) => {
40
+ const min = scale[name]
41
+ return value !== undefined && min !== undefined ? [{ min, value }] : []
42
+ })
43
+ }
44
+
22
45
  /**
23
46
  * Widest first, since `sizes` takes the first matching condition and the last
24
47
  * entry must be bare. `min-width` matches the Tailwind variant's boundary; a
25
48
  * `max-width: <next> - 1` one would leave a fractional-pixel gap.
26
49
  */
27
- function toSizes(
28
- base: string,
29
- overrides: Readonly<Record<string, string | undefined>>,
30
- scale: BreakpointMap
31
- ): string {
32
- const conditions: string[] = []
33
-
34
- for (const [name, min] of Object.entries(scale).sort(
35
- ([, a], [, b]) => b - a
36
- )) {
37
- const value = overrides[name]
38
- if (value !== undefined) {
39
- conditions.push(`(min-width: ${min}px) ${value}`)
40
- }
41
- }
50
+ function joinSizes(base: string, conditions: Condition<string>[]): string {
51
+ const sorted = [...conditions].sort((a, b) => b.min - a.min)
42
52
 
43
- return [...conditions, base].join(", ")
53
+ return [
54
+ ...sorted.map(({ min, value }) => `(min-width: ${min}px) ${value}`),
55
+ base,
56
+ ].join(", ")
44
57
  }
45
58
 
46
59
  /** Builds `sizes` from the CSS length the image renders at per breakpoint. */
47
60
  export function buildImageSizes<
48
61
  B extends BreakpointMap = typeof BASEMENT_BREAKPOINTS,
49
- >(widths: ByBreakpoint<string, B>, breakpoints?: B): string {
62
+ >(widths: ByBreakpoint<string, B>, options: ImageSizesOptions<B> = {}): string {
50
63
  const { base, ...overrides } = widths
51
- return toSizes(base, overrides, breakpoints ?? BASEMENT_BREAKPOINTS)
64
+ const scale = options.breakpoints ?? BASEMENT_BREAKPOINTS
65
+
66
+ return joinSizes(base, toConditions<string>(overrides, scale))
52
67
  }
53
68
 
54
69
  /**
@@ -58,15 +73,39 @@ export function buildImageSizes<
58
73
  */
59
74
  export function buildGridImageSizes<
60
75
  B extends BreakpointMap = typeof BASEMENT_BREAKPOINTS,
61
- >(columns: ByBreakpoint<number, B>, breakpoints?: B): string {
76
+ >(
77
+ columns: ByBreakpoint<number, B>,
78
+ options: GridImageSizesOptions<B> = {}
79
+ ): string {
80
+ const { maxWidth, breakpoints } = options
62
81
  const { base, ...overrides } = columns
63
- const widths: Record<string, string> = {}
82
+ const steps = toConditions<number>(
83
+ overrides,
84
+ breakpoints ?? BASEMENT_BREAKPOINTS
85
+ )
86
+ const cap =
87
+ maxWidth !== undefined && Number.isFinite(maxWidth) && maxWidth > 0
88
+ ? maxWidth
89
+ : undefined
90
+
91
+ // The cap rarely lands on a breakpoint, so it needs its own condition.
92
+ if (cap !== undefined && !steps.some(({ min }) => min === cap)) {
93
+ const below = steps
94
+ .filter(({ min }) => min < cap)
95
+ .sort((a, b) => a.min - b.min)
96
+ .at(-1)
64
97
 
65
- for (const [name, count] of Object.entries(overrides)) {
66
- if (count !== undefined) {
67
- widths[name] = columnWidth(count)
68
- }
98
+ steps.push({ min: cap, value: below?.value ?? base })
69
99
  }
70
100
 
71
- return toSizes(columnWidth(base), widths, breakpoints ?? BASEMENT_BREAKPOINTS)
101
+ return joinSizes(
102
+ columnWidth(base),
103
+ steps.map(({ min, value }) => ({
104
+ min,
105
+ value:
106
+ cap !== undefined && min >= cap
107
+ ? `${Math.round(cap / value)}px`
108
+ : columnWidth(value),
109
+ }))
110
+ )
72
111
  }
@@ -45,6 +45,7 @@ export const metadata = generatePageMetadata({ title: 'About' })
45
45
  // next/image sizes — keys mirror the element's Tailwind variants
46
46
  buildImageSizes({ base: '100vw', desktop: '796px' })
47
47
  buildGridImageSizes({ base: 1, tablet: 2, desktop: 3 })
48
+ buildGridImageSizes({ base: 1, tablet: 2, desktop: 3 }, { maxWidth: 1440 })
48
49
 
49
50
  // Refs — in a component, use the hook: an inline mergeRefs([...]) is a new
50
51
  // callback each render, so React detaches and reattaches every ref.
@@ -15,40 +15,55 @@ export type ByBreakpoint<
15
15
  B extends BreakpointMap = typeof BASEMENT_BREAKPOINTS,
16
16
  > = { base: T } & Partial<Record<Extract<keyof B, string>, T>>
17
17
 
18
+ export type ImageSizesOptions<
19
+ B extends BreakpointMap = typeof BASEMENT_BREAKPOINTS,
20
+ > = { breakpoints?: B }
21
+
22
+ export type GridImageSizesOptions<
23
+ B extends BreakpointMap = typeof BASEMENT_BREAKPOINTS,
24
+ > = ImageSizesOptions<B> & {
25
+ /** The grid container's `max-width` in px, past which columns stop growing. */
26
+ maxWidth?: number
27
+ }
28
+
29
+ type Condition<T> = { min: number; value: T }
30
+
18
31
  function columnWidth(columns: number): string {
19
32
  return `${Math.round(100 / columns)}vw`
20
33
  }
21
34
 
35
+ function toConditions<T>(
36
+ overrides: Readonly<Record<string, T | undefined>>,
37
+ scale: BreakpointMap
38
+ ): Condition<T>[] {
39
+ return Object.entries(overrides).flatMap(([name, value]) => {
40
+ const min = scale[name]
41
+ return value !== undefined && min !== undefined ? [{ min, value }] : []
42
+ })
43
+ }
44
+
22
45
  /**
23
46
  * Widest first, since `sizes` takes the first matching condition and the last
24
47
  * entry must be bare. `min-width` matches the Tailwind variant's boundary; a
25
48
  * `max-width: <next> - 1` one would leave a fractional-pixel gap.
26
49
  */
27
- function toSizes(
28
- base: string,
29
- overrides: Readonly<Record<string, string | undefined>>,
30
- scale: BreakpointMap
31
- ): string {
32
- const conditions: string[] = []
33
-
34
- for (const [name, min] of Object.entries(scale).sort(
35
- ([, a], [, b]) => b - a
36
- )) {
37
- const value = overrides[name]
38
- if (value !== undefined) {
39
- conditions.push(`(min-width: ${min}px) ${value}`)
40
- }
41
- }
50
+ function joinSizes(base: string, conditions: Condition<string>[]): string {
51
+ const sorted = [...conditions].sort((a, b) => b.min - a.min)
42
52
 
43
- return [...conditions, base].join(", ")
53
+ return [
54
+ ...sorted.map(({ min, value }) => `(min-width: ${min}px) ${value}`),
55
+ base,
56
+ ].join(", ")
44
57
  }
45
58
 
46
59
  /** Builds `sizes` from the CSS length the image renders at per breakpoint. */
47
60
  export function buildImageSizes<
48
61
  B extends BreakpointMap = typeof BASEMENT_BREAKPOINTS,
49
- >(widths: ByBreakpoint<string, B>, breakpoints?: B): string {
62
+ >(widths: ByBreakpoint<string, B>, options: ImageSizesOptions<B> = {}): string {
50
63
  const { base, ...overrides } = widths
51
- return toSizes(base, overrides, breakpoints ?? BASEMENT_BREAKPOINTS)
64
+ const scale = options.breakpoints ?? BASEMENT_BREAKPOINTS
65
+
66
+ return joinSizes(base, toConditions<string>(overrides, scale))
52
67
  }
53
68
 
54
69
  /**
@@ -58,15 +73,39 @@ export function buildImageSizes<
58
73
  */
59
74
  export function buildGridImageSizes<
60
75
  B extends BreakpointMap = typeof BASEMENT_BREAKPOINTS,
61
- >(columns: ByBreakpoint<number, B>, breakpoints?: B): string {
76
+ >(
77
+ columns: ByBreakpoint<number, B>,
78
+ options: GridImageSizesOptions<B> = {}
79
+ ): string {
80
+ const { maxWidth, breakpoints } = options
62
81
  const { base, ...overrides } = columns
63
- const widths: Record<string, string> = {}
82
+ const steps = toConditions<number>(
83
+ overrides,
84
+ breakpoints ?? BASEMENT_BREAKPOINTS
85
+ )
86
+ const cap =
87
+ maxWidth !== undefined && Number.isFinite(maxWidth) && maxWidth > 0
88
+ ? maxWidth
89
+ : undefined
90
+
91
+ // The cap rarely lands on a breakpoint, so it needs its own condition.
92
+ if (cap !== undefined && !steps.some(({ min }) => min === cap)) {
93
+ const below = steps
94
+ .filter(({ min }) => min < cap)
95
+ .sort((a, b) => a.min - b.min)
96
+ .at(-1)
64
97
 
65
- for (const [name, count] of Object.entries(overrides)) {
66
- if (count !== undefined) {
67
- widths[name] = columnWidth(count)
68
- }
98
+ steps.push({ min: cap, value: below?.value ?? base })
69
99
  }
70
100
 
71
- return toSizes(columnWidth(base), widths, breakpoints ?? BASEMENT_BREAKPOINTS)
101
+ return joinSizes(
102
+ columnWidth(base),
103
+ steps.map(({ min, value }) => ({
104
+ min,
105
+ value:
106
+ cap !== undefined && min >= cap
107
+ ? `${Math.round(cap / value)}px`
108
+ : columnWidth(value),
109
+ }))
110
+ )
72
111
  }