@contentrain/astro-kit 0.32.0 → 0.33.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 (85) hide show
  1. package/catalog.json +850 -19
  2. package/components/_shared/Button.astro +2 -1
  3. package/components/_shared/KitImage.astro +9 -4
  4. package/components/_shared/Section.astro +3 -1
  5. package/components/_shared/SectionHeader.astro +27 -0
  6. package/components/_shared/measured.ts +13 -0
  7. package/components/_shared/safe-url.ts +67 -0
  8. package/components/_shared/social.ts +73 -0
  9. package/components/announcement/Announcement.astro +2 -1
  10. package/components/announcement/meta.json +1 -1
  11. package/components/band/Band.astro +3 -2
  12. package/components/band/meta.json +1 -0
  13. package/components/breadcrumb/Breadcrumb.astro +2 -1
  14. package/components/breadcrumb/meta.json +1 -1
  15. package/components/card-grid/CardGrid.astro +2 -1
  16. package/components/card-grid/meta.json +1 -0
  17. package/components/consent/Consent.astro +2 -1
  18. package/components/consent/meta.json +1 -1
  19. package/components/contact-details/ContactDetails.astro +3 -2
  20. package/components/contact-details/meta.json +1 -0
  21. package/components/contact-form/ContactForm.astro +4 -3
  22. package/components/contact-form/meta.json +1 -0
  23. package/components/cta/meta.json +1 -1
  24. package/components/dialog/Dialog.astro +113 -0
  25. package/components/dialog/fixtures.json +84 -0
  26. package/components/dialog/meta.json +252 -0
  27. package/components/embed/meta.json +1 -1
  28. package/components/faq/fixtures.json +4 -0
  29. package/components/faq/meta.json +2 -2
  30. package/components/feature-list/meta.json +1 -0
  31. package/components/footer/Footer.astro +5 -4
  32. package/components/footer/meta.json +1 -0
  33. package/components/gallery/Gallery.astro +2 -1
  34. package/components/gallery/meta.json +1 -0
  35. package/components/header/Header.astro +17 -5
  36. package/components/header/fixtures.json +90 -1
  37. package/components/header/meta.json +5 -3
  38. package/components/hero/meta.json +1 -0
  39. package/components/language-switcher/LanguageSwitcher.astro +68 -0
  40. package/components/language-switcher/fixtures.json +51 -0
  41. package/components/language-switcher/meta.json +73 -0
  42. package/components/logo-cloud/LogoCloud.astro +2 -1
  43. package/components/logo-cloud/meta.json +1 -0
  44. package/components/nav/Nav.astro +77 -24
  45. package/components/nav/NavTree.astro +40 -0
  46. package/components/nav/fixtures.json +165 -0
  47. package/components/nav/meta.json +11 -2
  48. package/components/newsletter/Newsletter.astro +2 -1
  49. package/components/newsletter/meta.json +1 -0
  50. package/components/pagination/Pagination.astro +4 -3
  51. package/components/pagination/meta.json +1 -1
  52. package/components/post-card/PostCard.astro +4 -3
  53. package/components/post-card/meta.json +1 -1
  54. package/components/post-list/PostList.astro +3 -2
  55. package/components/post-list/meta.json +1 -0
  56. package/components/pricing/meta.json +1 -0
  57. package/components/search-box/SearchBox.astro +51 -0
  58. package/components/search-box/fixtures.json +31 -0
  59. package/components/search-box/meta.json +75 -0
  60. package/components/slider/Slider.astro +19 -115
  61. package/components/slider/SliderTrack.astro +178 -0
  62. package/components/slider/fixtures.json +2 -1
  63. package/components/slider/meta.json +11 -5
  64. package/components/social-links/SocialLinks.astro +74 -0
  65. package/components/social-links/fixtures.json +67 -0
  66. package/components/social-links/meta.json +94 -0
  67. package/components/split/meta.json +1 -0
  68. package/components/stats/Stats.astro +19 -8
  69. package/components/stats/fixtures.json +87 -0
  70. package/components/stats/meta.json +1 -0
  71. package/components/steps/meta.json +1 -0
  72. package/components/tabs/Tabs.astro +10 -3
  73. package/components/tabs/fixtures.json +4 -0
  74. package/components/tabs/meta.json +5 -2
  75. package/components/team/Team.astro +3 -2
  76. package/components/team/meta.json +2 -0
  77. package/components/team/team.ts +3 -8
  78. package/components/testimonial/meta.json +2 -3
  79. package/dist/index.mjs +1 -1
  80. package/dist/index.mjs.map +1 -1
  81. package/mapping/classic.json +117 -2
  82. package/mapping/divi.json +6 -5
  83. package/mapping/elementor.json +120 -7
  84. package/mapping/gutenberg.json +20 -8
  85. package/package.json +2 -2
@@ -12,14 +12,23 @@ const stats = tv({
12
12
  slots: {
13
13
  list: 'grid grid-cols-2 gap-6 md:gap-8',
14
14
  item: 'flex gap-1',
15
- value: 'text-4xl [font-weight:var(--font-weight-heading,700)] tracking-tight tabular-nums md:text-5xl',
15
+ // nowrap keeps a figure with its prefix and suffix ("99.9%" never splits into "99.9" / "%").
16
+ // The size is one pair of variables (valueSize sets them): --stat-max below md, --stat-max-md from md. Its line
17
+ // height comes with valueSize too: a font-size class merged in later would drop one set here.
18
+ value: 'text-[length:var(--stat-max,2.25rem)] [font-weight:var(--font-weight-heading,700)] tracking-tight tabular-nums whitespace-nowrap [--stat-max-md:3rem] md:text-[length:var(--stat-max-md)]',
16
19
  icon: 'mb-3 block size-10 object-contain',
17
20
  label: 'text-sm opacity-80',
18
21
  },
19
22
  variants: {
20
23
  // grid: figures in columns. inline: one line that wraps, for two to four short figures.
21
24
  layout: {
22
- grid: {},
25
+ // Each cell is a size container; a figure is no wider than the cell: its size is capped by 100cqi over its
26
+ // character count (--stat-chars, about 0.66em a character), so a long one ("1.250+", "€12.5M") in four columns at
27
+ // tablet width shrinks to fit the card, and a short one keeps the size valueSize gives it.
28
+ grid: {
29
+ item: '[container-type:inline-size] min-w-0',
30
+ value: 'text-[length:min(var(--stat-max,2.25rem),max(1.25rem,calc(100cqi_/_(var(--stat-chars,1)_*_0.66))))] md:text-[length:min(var(--stat-max-md),max(1.25rem,calc(100cqi_/_(var(--stat-chars,1)_*_0.66))))]',
31
+ },
23
32
  inline: { list: 'flex flex-wrap gap-x-12 gap-y-6 md:gap-x-16' },
24
33
  },
25
34
  columns: {
@@ -39,10 +48,10 @@ const stats = tv({
39
48
  align: { start: {}, center: { item: 'items-center text-center', icon: 'mx-auto' } },
40
49
  // The figure's size: md, lg (the default), xl, or a display number.
41
50
  valueSize: {
42
- md: { value: 'text-3xl md:text-4xl' },
43
- lg: {},
44
- xl: { value: 'text-5xl md:text-6xl' },
45
- display: { value: 'text-6xl md:text-7xl' },
51
+ md: { value: '[--stat-max:1.875rem] [--stat-max-md:2.25rem] leading-[1.2] md:leading-[1.1112]' },
52
+ lg: { value: 'leading-[1.1112] md:leading-none' },
53
+ xl: { value: '[--stat-max:3rem] [--stat-max-md:3.75rem] leading-none' },
54
+ display: { value: '[--stat-max:3.75rem] [--stat-max-md:4.5rem] leading-none' },
46
55
  },
47
56
  // accent/accent-ink is only ~5:1 — dimming text on it drops it under 4.5:1 (WCAG 1.4.3).
48
57
  tone: { default: {}, muted: {}, inverse: {}, accent: { label: 'opacity-100' } },
@@ -86,6 +95,8 @@ const s = stats({ layout, columns, style, align, valueSize, labelSize, order, to
86
95
  const grid = (layout ?? 'grid') === 'grid' ? measuredGrid(measured) : undefined
87
96
  const headingId = heading && id ? `${id}-title` : undefined
88
97
  const iconOf = (item: Item) => imageOf(item.icon, '')
98
+ // The figure's width in characters, for the size it may take in a narrow cell.
99
+ const widthOf = (item: Item) => `--stat-chars:${(item.prefix ?? '').length + item.value.length + (item.suffix ?? '').length}`
89
100
  // A <dl> names its term first, so figure-first is a plain list, read in the order it is drawn (WCAG 1.3.2).
90
101
  ---
91
102
  <Section measured={measured} tone={tone} id={id} aria-labelledby={headingId}>
@@ -99,7 +110,7 @@ const iconOf = (item: Item) => imageOf(item.icon, '')
99
110
  <ul class={s.list({ class: grid })} role="list">
100
111
  {items.map(item => (
101
112
  <li class={s.item()}>
102
- <p class={s.value()}>{iconOf(item) && <KitImage image={iconOf(item)!} sizes="40px" class={s.icon()} />}{item.prefix}{item.value}{item.suffix}</p>
113
+ <p class={s.value()} style={widthOf(item)}>{iconOf(item) && <KitImage image={iconOf(item)!} sizes="40px" class={s.icon()} />}{item.prefix}{item.value}{item.suffix}</p>
103
114
  <p class={s.label()}>{item.label}</p>
104
115
  </li>
105
116
  ))}
@@ -109,7 +120,7 @@ const iconOf = (item: Item) => imageOf(item.icon, '')
109
120
  {items.map(item => (
110
121
  <div class={s.item()}>
111
122
  <dt class={s.label()}>{item.label}</dt>
112
- <dd class={s.value()}>{iconOf(item) && <KitImage image={iconOf(item)!} sizes="40px" class={s.icon()} />}{item.prefix}{item.value}{item.suffix}</dd>
123
+ <dd class={s.value()} style={widthOf(item)}>{iconOf(item) && <KitImage image={iconOf(item)!} sizes="40px" class={s.icon()} />}{item.prefix}{item.value}{item.suffix}</dd>
113
124
  </div>
114
125
  ))}
115
126
  </dl>
@@ -329,5 +329,92 @@
329
329
  "tone": "dark"
330
330
  }
331
331
  }
332
+ },
333
+ {
334
+ "name": "long-values-four",
335
+ "props": {
336
+ "items": [
337
+ {
338
+ "value": "99.9",
339
+ "label": "Uptime",
340
+ "suffix": "%"
341
+ },
342
+ {
343
+ "value": "1.250",
344
+ "label": "Projects shipped",
345
+ "suffix": "+"
346
+ },
347
+ {
348
+ "value": "12.5",
349
+ "label": "Revenue handled",
350
+ "prefix": "€",
351
+ "suffix": "M"
352
+ },
353
+ {
354
+ "value": "480",
355
+ "label": "Clients served"
356
+ }
357
+ ],
358
+ "columns": "4",
359
+ "style": "card",
360
+ "tone": "muted"
361
+ }
362
+ },
363
+ {
364
+ "name": "long-values-two-xl",
365
+ "props": {
366
+ "items": [
367
+ {
368
+ "value": "99.9",
369
+ "label": "Uptime",
370
+ "suffix": "%"
371
+ },
372
+ {
373
+ "value": "1.250",
374
+ "label": "Projects shipped",
375
+ "suffix": "+"
376
+ },
377
+ {
378
+ "value": "12.5",
379
+ "label": "Revenue handled",
380
+ "prefix": "€",
381
+ "suffix": "M"
382
+ },
383
+ {
384
+ "value": "480",
385
+ "label": "Clients served"
386
+ }
387
+ ],
388
+ "columns": "2",
389
+ "style": "card",
390
+ "valueSize": "xl"
391
+ }
392
+ },
393
+ {
394
+ "name": "long-values-three-display",
395
+ "props": {
396
+ "items": [
397
+ {
398
+ "value": "99.9",
399
+ "label": "Uptime",
400
+ "suffix": "%"
401
+ },
402
+ {
403
+ "value": "1.250",
404
+ "label": "Projects shipped",
405
+ "suffix": "+"
406
+ },
407
+ {
408
+ "value": "12.5",
409
+ "label": "Revenue handled",
410
+ "prefix": "€",
411
+ "suffix": "M"
412
+ }
413
+ ],
414
+ "columns": "3",
415
+ "style": "card",
416
+ "valueSize": "display",
417
+ "align": "center"
418
+ }
332
419
  }
333
420
  ]
@@ -9,6 +9,7 @@
9
9
  "Section.astro",
10
10
  "image.ts",
11
11
  "measured.ts",
12
+ "safe-url.ts",
12
13
  "types.ts"
13
14
  ],
14
15
  "variants": {
@@ -9,6 +9,7 @@
9
9
  "Section.astro",
10
10
  "image.ts",
11
11
  "measured.ts",
12
+ "safe-url.ts",
12
13
  "types.ts"
13
14
  ],
14
15
  "variants": {
@@ -5,7 +5,8 @@
5
5
  // the same markup into tabs.
6
6
  import { tv } from 'tailwind-variants'
7
7
  import Section from '../_shared/Section.astro'
8
- import type { MeasuredStyle } from '../_shared/types'
8
+ import SectionHeader from '../_shared/SectionHeader.astro'
9
+ import type { ActionInput, MeasuredStyle } from '../_shared/types'
9
10
 
10
11
  export interface TabItem {
11
12
  label: string
@@ -42,6 +43,10 @@ const tabs = tv({
42
43
  interface Props {
43
44
  items: TabItem[]
44
45
  heading?: string | undefined
46
+ /** The rest of the section's head, drawn with the heading when any is given. */
47
+ intro?: string | undefined
48
+ eyebrow?: string | undefined
49
+ actions?: ActionInput[] | undefined
45
50
  /** Accessible name of the tab list when there is no heading. */
46
51
  label?: string | undefined
47
52
  style?: 'underline' | 'pills' | undefined
@@ -52,7 +57,7 @@ interface Props {
52
57
  measured?: MeasuredStyle | undefined
53
58
  }
54
59
 
55
- const { items, heading, label, style, orientation = 'horizontal', tone, id = 'tabs', measured } = Astro.props
60
+ const { items, heading, intro, eyebrow, actions, label, style, orientation = 'horizontal', tone, id = 'tabs', measured } = Astro.props
56
61
  const s = tabs({ style, orientation, tone })
57
62
  const headingId = heading ? `${id}-title` : undefined
58
63
  // One level under the section heading, or the section's heading when there is none.
@@ -61,7 +66,9 @@ const value = (i: number) => `tab-${i + 1}`
61
66
  ---
62
67
  {items.length > 0 && (
63
68
  <Section measured={measured} tone={tone} spacing="md" id={id} aria-labelledby={headingId}>
64
- {heading && <h2 id={headingId} class={s.heading()}>{heading}</h2>}
69
+ {intro || eyebrow || actions?.length
70
+ ? <SectionHeader heading={heading} intro={intro} eyebrow={eyebrow} actions={actions} headingId={headingId} />
71
+ : heading && <h2 id={headingId} class={s.heading()}>{heading}</h2>}
65
72
  <div class={s.root()} data-kit-tabs={id} data-orientation={orientation} data-label={label ?? heading ?? undefined}>
66
73
  <div class={s.list()} data-part="list">
67
74
  {items.map((item, i) => <button type="button" class={s.trigger()} data-part="trigger" data-value={value(i)}>{item.label}</button>)}
@@ -33,5 +33,9 @@
33
33
  {
34
34
  "name": "pills-accent",
35
35
  "props": { "style": "pills", "tone": "accent", "label": "Formats", "id": "formats", "items": [{ "label": "Online", "content": "<p>Read on the site.</p>" }, { "label": "Print", "content": "<p>Delivered each season.</p>" }] }
36
+ },
37
+ {
38
+ "name": "four-measured",
39
+ "props": { "eyebrow": "Plans", "heading": "The plan", "intro": "What each part of the plan includes.", "actions": [{ "label": "Compare plans", "href": "/plans/" }], "id": "plan", "items": [{ "label": "Overview", "content": "<p>What the plan covers.</p>" }, { "label": "Pricing", "content": "<p>What it costs each month.</p>" }, { "label": "Security", "content": "<p>How data is kept safe.</p>" }, { "label": "Support", "content": "<p>How to reach a person.</p>" }], "measured": { "padY": 40, "containerPx": 880 } }
36
40
  }
37
41
  ]
@@ -4,13 +4,16 @@
4
4
  "description": "Tabbed panels with the WAI-ARIA tabs pattern from Zag.js (arrow keys, Home/End, roving focus). Without script every panel shows in turn under its own title, so no content is ever hidden from a reader or a crawler.",
5
5
  "category": "content",
6
6
  "uses": [],
7
- "shared": ["Section.astro", "measured.ts", "types.ts"],
7
+ "shared": ["Button.astro", "Section.astro", "SectionHeader.astro", "measured.ts", "safe-url.ts", "types.ts"],
8
8
  "variants": {
9
9
  "style": { "options": ["underline", "pills"], "default": "underline", "description": "underline: a rule under the list, the selected tab underlined in accent. pills: the selected tab filled with accent." },
10
10
  "orientation": { "options": ["horizontal", "vertical"], "default": "horizontal", "description": "vertical: the tab list beside the panel on wide screens (Elementor vertical tabs)." },
11
11
  "tone": { "options": ["default", "muted", "inverse", "accent"], "default": "default", "description": "Section background." }
12
12
  },
13
13
  "props": {
14
+ "intro": {"type": "text", "label": "Introduction", "description": "A sentence under the heading."},
15
+ "eyebrow": {"type": "string", "label": "Eyebrow", "description": "A short line above the heading."},
16
+ "actions": {"type": "array", "label": "Buttons", "required": false, "items": {"type": "object", "fields": {"label": {"type": "string", "label": "Label", "required": true, "description": "The button text."}, "href": {"type": "url", "label": "Link", "required": true, "description": "Where the button goes: a page on this site or a full address."}, "newTab": {"type": "boolean", "label": "Open in a new tab", "description": "Open the link in a new tab (an external site the reader should not leave for)."}, "style": {"type": "select", "label": "Style", "options": ["primary", "secondary", "ghost"], "description": "primary for the main action, secondary or ghost for the ones beside it."}}}, "description": "Buttons under the intro, in order; none by default."},
14
17
  "measured": {"type": "object", "content": false, "description": "The source section's measured style (MeasuredStyle): frame width, padding, fill, image, columns per width, gap, text tone, radius, minimum height — all in pixels. Absent, the site's tokens; present, it wins over tone and columns. Values that are not a number, a colour or a known keyword are dropped.", "fields": {"containerPx": {"type": "integer", "min": 0, "max": 2560, "description": "The frame's content width."}, "padY": {"type": "integer", "min": 0, "max": 400, "description": "Top and bottom padding."}, "padX": {"type": "integer", "min": 0, "max": 200, "description": "The frame's side padding (gutter)."}, "bg": {"type": "object", "description": "The section's background.", "fields": {"color": {"type": "string", "description": "Fill colour: hex, rgb(a) or hsl(a)."}, "image": {"type": "string", "description": "Background image address (http(s) or root-relative), drawn behind the content."}, "overlay": {"type": "string", "description": "Colour or linear/radial gradient laid over the image or the fill."}, "size": {"type": "string", "description": "cover, contain or auto; anything else draws as cover."}, "position": {"type": "string", "description": "One or two of center/top/bottom/left/right or percentages."}}}, "columns": {"type": "object", "description": "Item columns at 390 (base), 768 (md) and 1280 (lg) px; a missing width follows the narrower one.", "fields": {"390": {"type": "integer", "min": 1, "max": 6, "description": "Phone."}, "768": {"type": "integer", "min": 1, "max": 6, "description": "Tablet."}, "1280": {"type": "integer", "min": 1, "max": 6, "description": "Desktop."}}}, "gap": {"type": "integer", "min": 0, "max": 200, "description": "Gap between items."}, "tone": {"type": "select", "options": ["light", "dark"], "description": "What the text is set for: dark = light text on a dark band. Without it, a fill's own lightness decides."}, "align": {"type": "select", "options": ["start", "center", "end"], "description": "Where the heading and lead sit; read by a component whose own align is not set."}, "radius": {"type": "integer", "min": 0, "max": 200, "description": "Card and item corner radius."}, "minHeight": {"type": "integer", "min": 0, "max": 2000, "description": "The section's minimum height."}}},
15
18
  "items": { "type": "array", "required": true, "items": { "type": "object", "fields": { "label": { "type": "string", "required": true }, "content": { "type": "richtext", "required": true } } }, "description": "Tabs in order (Elementor tabs tab_title/tab_content, Divi tab title + content)." },
16
19
  "heading": { "type": "string", "description": "Section heading; also names the tab list." },
@@ -23,7 +26,7 @@
23
26
  "sources": {
24
27
  "elementor": ["elementor/tabs", "elementor/nested-tabs"],
25
28
  "divi": ["divi/et_pb_tabs"],
26
- "classic": ["classic/tabs"]
29
+ "classic": ["classic/tabs", "classic/aria-tabs"]
27
30
  },
28
31
  "a11y": "Zag.js tabs: role=tablist/tab/tabpanel with aria-selected, aria-controls and aria-labelledby; arrow keys (per orientation), Home and End move between tabs. Before the script runs every panel is visible with its own title (an h3 under the section's h2, an h2 when there is no heading); after, the titles become screen-reader-only and the tabs label the panels."
29
32
  }
@@ -8,6 +8,7 @@ import { measuredGrid } from '../_shared/measured'
8
8
  import type { MeasuredStyle } from '../_shared/types'
9
9
  import { imageOf, type ImageSource } from '../_shared/image'
10
10
  import { profile } from './team'
11
+ import { safeHref } from '../_shared/safe-url'
11
12
 
12
13
  const team = tv({
13
14
  slots: {
@@ -105,7 +106,7 @@ const NameHeading = heading ? 'h3' : 'h2'
105
106
  {imageOf(member.image, member.imageAlt ?? member.name) && <div class={`${s.portrait()} kit-portrait`}><KitImage image={imageOf(member.image, member.imageAlt ?? member.name)!} sizes="(min-width: 1024px) 25vw, (min-width: 640px) 50vw, 100vw" class="aspect-square w-full object-cover" /></div>}
106
107
  <NameHeading class={s.name()}>
107
108
  {member.href
108
- ? <a href={member.href} class="text-current no-underline after:absolute after:inset-0 after:content-[''] hover:text-accent-text">{member.name}</a>
109
+ ? <a href={safeHref(member.href)} class="text-current no-underline after:absolute after:inset-0 after:content-[''] hover:text-accent-text">{member.name}</a>
109
110
  : member.name}
110
111
  </NameHeading>
111
112
  {member.role && <p class={s.role()}>{member.role}</p>}
@@ -120,7 +121,7 @@ const NameHeading = heading ? 'h3' : 'h2'
120
121
  {member.links && member.links.length > 0 && (
121
122
  <ul class={s.socials()}>
122
123
  {member.links.map(profile).map(link => (
123
- <li><a href={link.href} class={s.social()} aria-label={socialLabel.replace('{name}', member.name).replace('{network}', link.label)}>{link.label}</a></li>
124
+ <li><a href={safeHref(link.href)} class={s.social()} aria-label={socialLabel.replace('{name}', member.name).replace('{network}', link.label)}>{link.label}</a></li>
124
125
  ))}
125
126
  </ul>
126
127
  )}
@@ -9,6 +9,8 @@
9
9
  "Section.astro",
10
10
  "image.ts",
11
11
  "measured.ts",
12
+ "social.ts",
13
+ "safe-url.ts",
12
14
  "types.ts"
13
15
  ],
14
16
  "variants": {
@@ -1,9 +1,4 @@
1
- // The network a profile address belongs to, read from its host (`linkedin.com` → LinkedIn); an unknown host is shown as itself.
2
- const NETWORKS: Record<string, string> = { 'linkedin.com': 'LinkedIn', 'github.com': 'GitHub', 'x.com': 'X', 'twitter.com': 'X', 'instagram.com': 'Instagram', 'facebook.com': 'Facebook', 'youtube.com': 'YouTube', 'dribbble.com': 'Dribbble', 'behance.net': 'Behance', 'medium.com': 'Medium' }
1
+ import { addressLabel, networkOf } from '../_shared/social'
3
2
 
4
- export const profile = (href: string) => {
5
- let host = href
6
- try { host = new URL(href).hostname.replace(/^www\./, '') } catch { /* a relative address keeps its own text */ }
7
- // `mailto:` and `tel:` addresses have no host: the address itself is the label, never an empty one.
8
- return { href, label: NETWORKS[host] ?? (host || href) }
9
- }
3
+ /** A profile address and the label it is announced and shown by: the network its host names, else the host, else the address itself (`mailto:`, `tel:`, relative). */
4
+ export const profile = (href: string) => ({ href, label: networkOf(href)?.label ?? addressLabel(href) })
@@ -9,6 +9,7 @@
9
9
  "Section.astro",
10
10
  "image.ts",
11
11
  "measured.ts",
12
+ "safe-url.ts",
12
13
  "types.ts"
13
14
  ],
14
15
  "variants": {
@@ -266,9 +267,7 @@
266
267
  "js": "none",
267
268
  "dependencies": {},
268
269
  "sources": {
269
- "elementor": [
270
- "elementor/testimonial"
271
- ],
270
+ "elementor": ["elementor/testimonial", "elementor/testimonial-carousel", "elementor/reviews"],
272
271
  "divi": [
273
272
  "divi/et_pb_testimonial"
274
273
  ],
package/dist/index.mjs CHANGED
@@ -137,7 +137,7 @@ async function copyComponents(plan, kitRoot, siteRoot) {
137
137
  //#endregion
138
138
  //#region src/mapping.ts
139
139
  const MAPPING_FORMAT = "astro-kit-mapping@1";
140
- const MAPPING_VALUE_PATTERN = /^(?:field:[\w-]+|media:[\w-]+|ref:[\w-]+\.[\w-]+|href:(?:self|[\w-]+)|term:[\w-]+|ui:[\w.-]+|const:(?:true|false|-?\d+(?:\.\d+)?|[a-z][a-z0-9_-]{0,31})|site:[\w-]+|menu:(?:primary|footer)|page:(?:base|current|total|breadcrumb)|attr:[\w.-]+|class:[\w*-]+=(?:[a-z][\w-]*)?\|(?:[a-z][\w-]*)?|(?:dom|img|link|html):[^@]*(?:@[\w-]+)?)$/;
140
+ const MAPPING_VALUE_PATTERN = /^(?:field:[\w-]+|media:[\w-]+|ref:[\w-]+\.[\w-]+|href:(?:self|[\w-]+)|term:[\w-]+|ui:[\w.-]+|const:(?:true|false|-?\d+(?:\.\d+)?|[a-z][a-z0-9_-]{0,31})|site:[\w-]+|menu:(?:primary|footer)|page:(?:base|current|total|breadcrumb)|attr:[\w.-]+|class:[\w*-]+=(?:[a-z][\w-]*)?\|(?:[a-z][\w-]*)?|(?:dom|img|link|html)(?:@root)?:[^@]*(?:@[\w-]+)?)$/;
141
141
  /** The id a plan records for a section rule. */
142
142
  function sectionRuleId(builder, rule) {
143
143
  return `${builder}:section:${rule.id}`;