@purestack/ts-components 1.1.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.
Files changed (106) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +10 -0
  3. package/dist/ts-components.d.mts +1662 -0
  4. package/dist/ts-components.d.mts.map +1 -0
  5. package/dist/ts-components.mjs +9200 -0
  6. package/dist/ts-components.mjs.map +1 -0
  7. package/package.json +130 -0
  8. package/src/defineComponents.ts +87 -0
  9. package/src/index.ts +270 -0
  10. package/src/registerStyles.ts +63 -0
  11. package/src/standard/alertBox/alertBox.ts +64 -0
  12. package/src/standard/autoId.ts +25 -0
  13. package/src/standard/badge/badge.ts +41 -0
  14. package/src/standard/badge/badgeStyle.ts +21 -0
  15. package/src/standard/barChart/barChart.ts +338 -0
  16. package/src/standard/barChart/barChartStyle.ts +81 -0
  17. package/src/standard/btn/btn.ts +191 -0
  18. package/src/standard/btn/btnStyle.ts +59 -0
  19. package/src/standard/btnGroup/btnGroup.ts +208 -0
  20. package/src/standard/btnGroup/btnGroupStyle.ts +85 -0
  21. package/src/standard/chart/cartesianChart.ts +99 -0
  22. package/src/standard/chart/chartDefaults.ts +25 -0
  23. package/src/standard/chart/chartUtils.ts +84 -0
  24. package/src/standard/classicLogo/classicLogo.ts +429 -0
  25. package/src/standard/classicLogo/classicLogoStyle.ts +292 -0
  26. package/src/standard/componentVariant.ts +153 -0
  27. package/src/standard/composer/composer.ts +1035 -0
  28. package/src/standard/composer/composerCanvasInitialStyle.ts +225 -0
  29. package/src/standard/composer/composerCanvasRevertStyle.ts +14 -0
  30. package/src/standard/composer/composerCanvasSharedStyle.ts +60 -0
  31. package/src/standard/composer/composerCanvasStyleTypes.ts +8 -0
  32. package/src/standard/composer/composerCanvasThemeStyle.ts +53 -0
  33. package/src/standard/composer/composerStyle.ts +156 -0
  34. package/src/standard/composer/emailHtmlPolicy.ts +474 -0
  35. package/src/standard/consent/consent.ts +173 -0
  36. package/src/standard/consent/consentStyle.ts +44 -0
  37. package/src/standard/contactForm/contactForm.ts +135 -0
  38. package/src/standard/contactForm/contactFormStyle.ts +116 -0
  39. package/src/standard/doughnutChart/doughnutChart.ts +527 -0
  40. package/src/standard/doughnutChart/doughnutChartStyle.ts +64 -0
  41. package/src/standard/dropFiles/dropFiles.ts +480 -0
  42. package/src/standard/dropFiles/dropFilesStyle.ts +171 -0
  43. package/src/standard/expandablePanel/expandablePanel.ts +106 -0
  44. package/src/standard/expandablePanel/expandablePanelStyle.ts +56 -0
  45. package/src/standard/flex/flex.ts +269 -0
  46. package/src/standard/flex/flexStyle.ts +556 -0
  47. package/src/standard/footer/footer.ts +93 -0
  48. package/src/standard/footer/footerStyle.ts +39 -0
  49. package/src/standard/form/autoCompleteInput.ts +567 -0
  50. package/src/standard/form/form.ts +205 -0
  51. package/src/standard/form/formCheck.ts +96 -0
  52. package/src/standard/form/formInputField.ts +184 -0
  53. package/src/standard/form/formSelectField.ts +152 -0
  54. package/src/standard/form/formStyle.ts +585 -0
  55. package/src/standard/form/multiAutoCompleteInput.ts +880 -0
  56. package/src/standard/grid/grid.ts +177 -0
  57. package/src/standard/grid/gridStyle.ts +56 -0
  58. package/src/standard/icon/icon.ts +96 -0
  59. package/src/standard/icon/iconStyle.ts +50 -0
  60. package/src/standard/landing/codeShowcase.ts +54 -0
  61. package/src/standard/landing/comparisonColumn.ts +30 -0
  62. package/src/standard/landing/comparisonFeature.ts +22 -0
  63. package/src/standard/landing/comparisonTable.ts +38 -0
  64. package/src/standard/landing/ctaSection.ts +59 -0
  65. package/src/standard/landing/featureCard.ts +41 -0
  66. package/src/standard/landing/landing.ts +52 -0
  67. package/src/standard/landing/landingBand.ts +217 -0
  68. package/src/standard/landing/landingSection.ts +43 -0
  69. package/src/standard/landing/landingTypes.ts +141 -0
  70. package/src/standard/landing/metricItem.ts +25 -0
  71. package/src/standard/landing/metricStrip.ts +20 -0
  72. package/src/standard/lineChart/lineChart.ts +462 -0
  73. package/src/standard/lineChart/lineChartStyle.ts +91 -0
  74. package/src/standard/logo/logo.ts +192 -0
  75. package/src/standard/logo/logoStyle.ts +164 -0
  76. package/src/standard/modal/modal.ts +171 -0
  77. package/src/standard/modal/modalStore.ts +154 -0
  78. package/src/standard/modal/modalStyle.ts +190 -0
  79. package/src/standard/navMenu/navMenu.ts +278 -0
  80. package/src/standard/navMenu/navMenuStyle.ts +323 -0
  81. package/src/standard/pageLinks/pageLinks.ts +90 -0
  82. package/src/standard/pageScript/pageScript.ts +155 -0
  83. package/src/standard/pageToc/pageToc.ts +154 -0
  84. package/src/standard/pageToc/pageTocStyle.ts +372 -0
  85. package/src/standard/panel/panel.ts +55 -0
  86. package/src/standard/panel/panelStyle.ts +15 -0
  87. package/src/standard/pricing/pricing.ts +146 -0
  88. package/src/standard/searchBox/searchBox.ts +54 -0
  89. package/src/standard/searchBox/searchBoxStyle.ts +180 -0
  90. package/src/standard/sectionHeader/sectionHeader.ts +99 -0
  91. package/src/standard/signIn/signIn.ts +167 -0
  92. package/src/standard/signIn/signInStyle.ts +70 -0
  93. package/src/standard/tabs/tabs.ts +193 -0
  94. package/src/standard/tabs/tabsStyle.ts +231 -0
  95. package/src/standard/themeToggle/themeToggle.ts +33 -0
  96. package/src/standard/themeToggle/themeToggleStyle.ts +88 -0
  97. package/src/standard/toast/toastHost.ts +42 -0
  98. package/src/standard/toast/toastStore.ts +50 -0
  99. package/src/standard/topBar/topBar.ts +101 -0
  100. package/src/standard/topBar/topBarStyle.ts +169 -0
  101. package/src/standard/virtualList/variableVirtualTable.ts +371 -0
  102. package/src/standard/virtualList/virtualList.ts +423 -0
  103. package/src/standard/virtualList/virtualListStyle.ts +34 -0
  104. package/src/standard/virtualList/virtualTable.ts +179 -0
  105. package/src/standard/virtualList/virtualTableStyle.ts +24 -0
  106. package/tsconfig.json +23 -0
@@ -0,0 +1,474 @@
1
+ export const GMAIL_COMPATIBLE_EMAIL_TAGS = new Set([
2
+ 'A',
3
+ 'ADDRESS',
4
+ 'B',
5
+ 'BLOCKQUOTE',
6
+ 'BODY',
7
+ 'BR',
8
+ 'CAPTION',
9
+ 'CENTER',
10
+ 'CITE',
11
+ 'CODE',
12
+ 'COL',
13
+ 'COLGROUP',
14
+ 'DD',
15
+ 'DEL',
16
+ 'DIV',
17
+ 'DL',
18
+ 'DT',
19
+ 'EM',
20
+ 'FONT',
21
+ 'H1',
22
+ 'H2',
23
+ 'H3',
24
+ 'H4',
25
+ 'H5',
26
+ 'H6',
27
+ 'HTML',
28
+ 'HR',
29
+ 'I',
30
+ 'IMG',
31
+ 'INS',
32
+ 'KBD',
33
+ 'LI',
34
+ 'OL',
35
+ 'P',
36
+ 'PRE',
37
+ 'S',
38
+ 'SAMP',
39
+ 'SMALL',
40
+ 'SPAN',
41
+ 'STRONG',
42
+ 'STYLE',
43
+ 'SUB',
44
+ 'SUP',
45
+ 'TABLE',
46
+ 'TBODY',
47
+ 'TD',
48
+ 'TFOOT',
49
+ 'TH',
50
+ 'THEAD',
51
+ 'TR',
52
+ 'TT',
53
+ 'U',
54
+ 'UL',
55
+ 'VAR',
56
+ ])
57
+
58
+ export const GMAIL_SUPPORTED_CSS_PROPERTIES = new Set([
59
+ 'azimuth',
60
+ 'background',
61
+ 'background-blend-mode',
62
+ 'background-clip',
63
+ 'background-color',
64
+ 'background-image',
65
+ 'background-origin',
66
+ 'background-position',
67
+ 'background-repeat',
68
+ 'background-size',
69
+ 'border',
70
+ 'border-bottom',
71
+ 'border-bottom-color',
72
+ 'border-bottom-left-radius',
73
+ 'border-bottom-right-radius',
74
+ 'border-bottom-style',
75
+ 'border-bottom-width',
76
+ 'border-collapse',
77
+ 'border-color',
78
+ 'border-left',
79
+ 'border-left-color',
80
+ 'border-left-style',
81
+ 'border-left-width',
82
+ 'border-radius',
83
+ 'border-right',
84
+ 'border-right-color',
85
+ 'border-right-style',
86
+ 'border-right-width',
87
+ 'border-spacing',
88
+ 'border-style',
89
+ 'border-top',
90
+ 'border-top-color',
91
+ 'border-top-left-radius',
92
+ 'border-top-right-radius',
93
+ 'border-top-style',
94
+ 'border-top-width',
95
+ 'border-width',
96
+ 'box-sizing',
97
+ 'break-after',
98
+ 'break-before',
99
+ 'break-inside',
100
+ 'caption-side',
101
+ 'clear',
102
+ 'color',
103
+ 'column-count',
104
+ 'column-fill',
105
+ 'column-gap',
106
+ 'column-rule',
107
+ 'column-rule-color',
108
+ 'column-rule-style',
109
+ 'column-rule-width',
110
+ 'column-span',
111
+ 'column-width',
112
+ 'columns',
113
+ 'direction',
114
+ 'display',
115
+ 'elevation',
116
+ 'empty-cells',
117
+ 'float',
118
+ 'font',
119
+ 'font-family',
120
+ 'font-feature-settings',
121
+ 'font-kerning',
122
+ 'font-size',
123
+ 'font-size-adjust',
124
+ 'font-stretch',
125
+ 'font-style',
126
+ 'font-synthesis',
127
+ 'font-variant',
128
+ 'font-variant-alternates',
129
+ 'font-variant-caps',
130
+ 'font-variant-east-asian',
131
+ 'font-variant-ligatures',
132
+ 'font-variant-numeric',
133
+ 'font-weight',
134
+ 'height',
135
+ 'image-orientation',
136
+ 'image-resolution',
137
+ 'ime-mode',
138
+ 'isolation',
139
+ 'layout-flow',
140
+ 'layout-grid',
141
+ 'layout-grid-char',
142
+ 'layout-grid-char-spacing',
143
+ 'layout-grid-line',
144
+ 'layout-grid-mode',
145
+ 'layout-grid-type',
146
+ 'letter-spacing',
147
+ 'line-break',
148
+ 'line-height',
149
+ 'list-style',
150
+ 'list-style-position',
151
+ 'list-style-type',
152
+ 'margin',
153
+ 'margin-bottom',
154
+ 'margin-left',
155
+ 'margin-right',
156
+ 'margin-top',
157
+ 'marker-offset',
158
+ 'max-height',
159
+ 'max-width',
160
+ 'min-height',
161
+ 'min-width',
162
+ 'mix-blend-mode',
163
+ 'object-fit',
164
+ 'object-position',
165
+ 'opacity',
166
+ 'outline',
167
+ 'outline-color',
168
+ 'outline-style',
169
+ 'outline-width',
170
+ 'overflow',
171
+ 'overflow-x',
172
+ 'overflow-y',
173
+ 'padding',
174
+ 'padding-bottom',
175
+ 'padding-left',
176
+ 'padding-right',
177
+ 'padding-top',
178
+ 'page-break-after',
179
+ 'page-break-before',
180
+ 'page-break-inside',
181
+ 'pause',
182
+ 'pause-after',
183
+ 'pause-before',
184
+ 'pitch',
185
+ 'pitch-range',
186
+ 'quotes',
187
+ 'richness',
188
+ 'speak',
189
+ 'speak-header',
190
+ 'speak-numeral',
191
+ 'speak-punctuation',
192
+ 'speech-rate',
193
+ 'stress',
194
+ 'table-layout',
195
+ 'text-align',
196
+ 'text-align-last',
197
+ 'text-autospace',
198
+ 'text-combine-upright',
199
+ 'text-decoration',
200
+ 'text-decoration-color',
201
+ 'text-decoration-line',
202
+ 'text-decoration-skip',
203
+ 'text-decoration-style',
204
+ 'text-emphasis',
205
+ 'text-emphasis-color',
206
+ 'text-emphasis-style',
207
+ 'text-indent',
208
+ 'text-justify',
209
+ 'text-kashida-space',
210
+ 'text-orientation',
211
+ 'text-overflow',
212
+ 'text-transform',
213
+ 'text-underline-position',
214
+ 'unicode-bidi',
215
+ 'vertical-align',
216
+ 'voice-family',
217
+ 'white-space',
218
+ 'width',
219
+ 'word-break',
220
+ 'word-spacing',
221
+ 'word-wrap',
222
+ 'writing-mode',
223
+ 'zoom',
224
+ ])
225
+
226
+ const IMAGE_URL_CSS_PROPERTIES = new Set([
227
+ 'background',
228
+ 'background-image',
229
+ 'list-style',
230
+ ])
231
+
232
+ const CSS_URL_PATTERN = /url\(\s*(?:"([^"]*)"|'([^']*)'|([^)]*?))\s*\)/gi
233
+
234
+ export interface EmailHtmlPolicyOptions {
235
+ imageUrlResolver?: (url: string) => string | undefined
236
+ }
237
+
238
+ export function copySafeEmailStyles(
239
+ source: HTMLElement,
240
+ target: HTMLElement,
241
+ options: EmailHtmlPolicyOptions = {},
242
+ ) {
243
+ applySafeEmailStyles(source.getAttribute('style'), target, options)
244
+ }
245
+
246
+ export function applySafeEmailStyles(
247
+ style: string | null | undefined,
248
+ target: HTMLElement,
249
+ options: EmailHtmlPolicyOptions = {},
250
+ ) {
251
+ if (!style) return
252
+ for (const item of splitCssDeclarations(style)) {
253
+ const separator = findCssDeclarationSeparator(item)
254
+ if (separator <= 0) continue
255
+
256
+ const property = item.slice(0, separator).trim().toLowerCase()
257
+ const value = sanitizeEmailStyleValue(
258
+ property,
259
+ item.slice(separator + 1),
260
+ options,
261
+ )
262
+ if (value) target.style.setProperty(property, value)
263
+ }
264
+ }
265
+
266
+ function splitCssDeclarations(style: string) {
267
+ const declarations: string[] = []
268
+ let start = 0
269
+ let quote = ''
270
+ let parenDepth = 0
271
+ let escaped = false
272
+
273
+ for (let i = 0; i < style.length; ++i) {
274
+ const char = style[i]
275
+ if (escaped) {
276
+ escaped = false
277
+ continue
278
+ }
279
+
280
+ if (char === '\\') {
281
+ escaped = true
282
+ continue
283
+ }
284
+
285
+ if (quote) {
286
+ if (char === quote) quote = ''
287
+ continue
288
+ }
289
+
290
+ if (char === '"' || char === "'") {
291
+ quote = char
292
+ continue
293
+ }
294
+
295
+ if (char === '(') {
296
+ ++parenDepth
297
+ continue
298
+ }
299
+
300
+ if (char === ')') {
301
+ if (parenDepth > 0) --parenDepth
302
+ continue
303
+ }
304
+
305
+ if (char === ';' && parenDepth === 0) {
306
+ declarations.push(style.slice(start, i))
307
+ start = i + 1
308
+ }
309
+ }
310
+
311
+ declarations.push(style.slice(start))
312
+ return declarations
313
+ }
314
+
315
+ function findCssDeclarationSeparator(declaration: string) {
316
+ let quote = ''
317
+ let parenDepth = 0
318
+ let escaped = false
319
+
320
+ for (let i = 0; i < declaration.length; ++i) {
321
+ const char = declaration[i]
322
+ if (escaped) {
323
+ escaped = false
324
+ continue
325
+ }
326
+
327
+ if (char === '\\') {
328
+ escaped = true
329
+ continue
330
+ }
331
+
332
+ if (quote) {
333
+ if (char === quote) quote = ''
334
+ continue
335
+ }
336
+
337
+ if (char === '"' || char === "'") {
338
+ quote = char
339
+ continue
340
+ }
341
+
342
+ if (char === '(') {
343
+ ++parenDepth
344
+ continue
345
+ }
346
+
347
+ if (char === ')') {
348
+ if (parenDepth > 0) --parenDepth
349
+ continue
350
+ }
351
+
352
+ if (char === ':' && parenDepth === 0) return i
353
+ }
354
+
355
+ return -1
356
+ }
357
+
358
+ export function sanitizeEmailStyleValue(
359
+ property: string,
360
+ value: string,
361
+ options: EmailHtmlPolicyOptions = {},
362
+ ) {
363
+ if (!GMAIL_SUPPORTED_CSS_PROPERTIES.has(property)) return undefined
364
+
365
+ const trimmed = stripImportant(value.trim())
366
+ if (!isSafeCssValue(trimmed)) return undefined
367
+
368
+ CSS_URL_PATTERN.lastIndex = 0
369
+ if (CSS_URL_PATTERN.test(trimmed)) {
370
+ CSS_URL_PATTERN.lastIndex = 0
371
+ if (!IMAGE_URL_CSS_PROPERTIES.has(property)) return undefined
372
+ const rewritten = rewriteCssUrls(trimmed, options)
373
+ CSS_URL_PATTERN.lastIndex = 0
374
+ return rewritten
375
+ }
376
+
377
+ CSS_URL_PATTERN.lastIndex = 0
378
+ return trimmed
379
+ }
380
+
381
+ export function sanitizePlainAttribute(value: string) {
382
+ let text = ''
383
+ for (let i = 0; i < value.length; ++i) {
384
+ const code = value.charCodeAt(i)
385
+ if (code >= 32 && code !== 127) text += value[i]
386
+ }
387
+
388
+ return text.replace(/\s+/g, ' ').trim()
389
+ }
390
+
391
+ export function toSafeEmailImageUrl(value: string) {
392
+ const trimmed = value.trim()
393
+ if (!trimmed) return undefined
394
+
395
+ const lower = trimmed.toLowerCase()
396
+ if (lower.startsWith('cid:') || lower.startsWith('data:image/'))
397
+ return trimmed
398
+
399
+ try {
400
+ const url = new URL(
401
+ trimmed,
402
+ globalThis.location?.href ?? 'https://localhost/',
403
+ )
404
+ if (url.protocol !== 'http:' && url.protocol !== 'https:') return undefined
405
+ return url.href
406
+ } catch {
407
+ return undefined
408
+ }
409
+ }
410
+
411
+ export function normalizeEmailCidUrl(value: string) {
412
+ const trimmed = value.trim()
413
+ if (!trimmed.toLowerCase().startsWith('cid:')) return undefined
414
+
415
+ const rawContentId = trimmed.slice(4)
416
+ let decodedContentId = rawContentId
417
+ try {
418
+ decodedContentId = decodeURIComponent(rawContentId)
419
+ } catch {
420
+ decodedContentId = rawContentId
421
+ }
422
+
423
+ return normalizeEmailContentId(decodedContentId) || undefined
424
+ }
425
+
426
+ export function normalizeEmailContentId(value: string) {
427
+ let contentId = value.trim()
428
+ if (contentId.length >= 2 && contentId[0] === '<' && contentId.at(-1) === '>')
429
+ contentId = contentId.slice(1, -1).trim()
430
+
431
+ return contentId.toLowerCase()
432
+ }
433
+
434
+ function stripImportant(value: string) {
435
+ return value.replace(/\s*!important\s*$/i, '').trim()
436
+ }
437
+
438
+ function isSafeCssValue(value: string) {
439
+ return (
440
+ value.length > 0 &&
441
+ value.length <= 512 &&
442
+ !hasUnsafeCssCharacter(value) &&
443
+ !/(?:expression|behavior|binding)\s*\(/i.test(value) &&
444
+ !/(?:javascript|vbscript)\s*:/i.test(value) &&
445
+ !/@import/i.test(value)
446
+ )
447
+ }
448
+
449
+ function rewriteCssUrls(value: string, options: EmailHtmlPolicyOptions) {
450
+ return value.replace(
451
+ CSS_URL_PATTERN,
452
+ (_match, doubleUrl, singleUrl, bareUrl) => {
453
+ const rawUrl = doubleUrl ?? singleUrl ?? bareUrl ?? ''
454
+ const proxiedUrl = options.imageUrlResolver
455
+ ? options.imageUrlResolver(rawUrl.trim())
456
+ : toSafeEmailImageUrl(rawUrl.trim())
457
+ return proxiedUrl ? `url("${escapeCssUrl(proxiedUrl)}")` : ''
458
+ },
459
+ )
460
+ }
461
+
462
+ function hasUnsafeCssCharacter(value: string) {
463
+ for (let i = 0; i < value.length; ++i) {
464
+ const code = value.charCodeAt(i)
465
+ if (code < 32 || code === 127 || value[i] === '<' || value[i] === '>')
466
+ return true
467
+ }
468
+
469
+ return false
470
+ }
471
+
472
+ function escapeCssUrl(value: string) {
473
+ return value.replace(/["\\\r\n]/g, '')
474
+ }
@@ -0,0 +1,173 @@
1
+ import type {
2
+ ConsentCategory,
3
+ ConsentConfig,
4
+ TsSsgContext,
5
+ } from '@purestack/ts-common'
6
+ import { resolveTsSsgContext } from '@purestack/ts-common'
7
+ import type { SemanticTone } from '@purestack/ts-style'
8
+ import { defineComponent, html, type RefOrValue } from 'regor'
9
+ import type {
10
+ ComponentVariant,
11
+ ComponentVariantMode,
12
+ } from '../componentVariant'
13
+
14
+ export interface ConsentCategoryState extends ConsentCategory {
15
+ inputId?: string
16
+ }
17
+
18
+ export interface Consent extends ConsentConfig {
19
+ settingsTeleport?: string
20
+ categories: ConsentCategoryState[]
21
+ tone?: RefOrValue<SemanticTone>
22
+ variant?: RefOrValue<ComponentVariant>
23
+ variantMode?: RefOrValue<ComponentVariantMode>
24
+ }
25
+
26
+ const consentTemplate = html`<section class="consent" data-consent-root r-if="enabled">
27
+ <aside
28
+ class="consent__banner"
29
+ data-consent-banner
30
+ role="dialog"
31
+ aria-live="polite"
32
+ aria-modal="false"
33
+ hidden
34
+ aria-hidden="true"
35
+ >
36
+ <Panel
37
+ :tone="tone || 'neutral'"
38
+ :variant="variant || 'surfaceAlt'"
39
+ :variantMode="variantMode"
40
+ >
41
+ <Flex direction="column" align="start">
42
+ <strong>{{ bannerTitle }}</strong>
43
+ <span>{{ bannerDescription }}</span>
44
+ <BtnLink
45
+ r-if="privacyPolicyUrl"
46
+ :href="privacyPolicyUrl"
47
+ tone="neutral"
48
+ variant="link"
49
+ >
50
+ {{ privacyPolicyLabel }}
51
+ </BtnLink>
52
+ <Flex align="center" wrap="true">
53
+ <Btn tone="accent" data-consent-action="accept-all">
54
+ {{ acceptAllLabel }}
55
+ </Btn>
56
+ <Btn tone="neutral" data-consent-action="reject-all">
57
+ {{ rejectAllLabel }}
58
+ </Btn>
59
+ <Btn tone="neutral" data-consent-action="open-panel">
60
+ {{ manageLabel }}
61
+ </Btn>
62
+ </Flex>
63
+ </Flex>
64
+ </Panel>
65
+ </aside>
66
+
67
+ <section
68
+ class="consent__panel"
69
+ data-consent-panel
70
+ role="dialog"
71
+ aria-modal="false"
72
+ hidden
73
+ aria-hidden="true"
74
+ >
75
+ <Panel
76
+ :tone="tone || 'neutral'"
77
+ :variant="variant || 'surfaceAlt'"
78
+ :variantMode="variantMode"
79
+ >
80
+ <Flex direction="column" align="start">
81
+ <Flex align="center" justify="between" class="w-full">
82
+ <strong>{{ manageLabel }}</strong>
83
+ <Btn
84
+ tone="ghost"
85
+ size="sm"
86
+ data-consent-action="close-panel"
87
+ aria-label="Close privacy settings"
88
+ >
89
+ <span aria-hidden="true">X</span>
90
+ </Btn>
91
+ </Flex>
92
+
93
+ <Flex direction="column" align="stretch" class="w-full">
94
+ <Panel
95
+ r-for="category in categories"
96
+ tone="neutral"
97
+ variant="outline"
98
+ >
99
+ <Flex direction="column" align="start">
100
+ <FormCheck
101
+ :id="category.inputId"
102
+ :label="category.label"
103
+ :checked="category.required"
104
+ :disabled="category.required"
105
+ :described-by="category.description ? category.inputId + '-desc' : undefined"
106
+ :data-consent-category-id="category.id"/>
107
+ <small
108
+ r-if="category.description"
109
+ :id="category.inputId + '-desc'"
110
+ >
111
+ {{ category.description }}
112
+ </small>
113
+ </Flex>
114
+ </Panel>
115
+ </Flex>
116
+
117
+ <Flex align="center" wrap="true">
118
+ <Btn tone="accent" data-consent-action="save"> {{ saveLabel }} </Btn>
119
+ <Btn tone="accent" data-consent-action="accept-all">
120
+ {{ acceptAllLabel }}
121
+ </Btn>
122
+ <Btn tone="neutral" data-consent-action="reject-all">
123
+ {{ rejectAllLabel }}
124
+ </Btn>
125
+ </Flex>
126
+ </Flex>
127
+ </Panel>
128
+ </section>
129
+
130
+ <Btn
131
+ tone="ghost"
132
+ size="sm"
133
+ data-consent-settings
134
+ data-consent-action="open-panel"
135
+ :r-teleport="settingsTeleport"
136
+ hidden
137
+ >
138
+ {{ settingsLabel }}
139
+ </Btn>
140
+ </section>`
141
+
142
+ function defineConsentComponent() {
143
+ return defineComponent<Consent>(consentTemplate, {
144
+ props: ['tone', 'variant', 'variantMode'],
145
+ context: (head) => resolveConsent(resolveTsSsgContext(head), head.props),
146
+ })
147
+ }
148
+
149
+ function resolveConsent(
150
+ context: TsSsgContext,
151
+ props: Partial<Consent>,
152
+ ): Consent {
153
+ const consent = context.site.consent
154
+ const categories = consent.categories.map((category) => ({
155
+ id: category.id,
156
+ label: category.label,
157
+ description: category.description,
158
+ required: category.required === true || category.id === 'necessary',
159
+ inputId: `consent-category-${category.id}`,
160
+ }))
161
+ return {
162
+ ...consent,
163
+ tone: props.tone,
164
+ variant: props.variant,
165
+ variantMode: props.variantMode,
166
+ settingsTeleport: '.consent-settings-teleport-area',
167
+ categories,
168
+ }
169
+ }
170
+
171
+ export function defineConsentComponents() {
172
+ return { consent: defineConsentComponent() }
173
+ }
@@ -0,0 +1,44 @@
1
+ import {
2
+ BREAKPOINTS,
3
+ getBreakpoint,
4
+ styleBuilder,
5
+ type ThemeMode,
6
+ themes,
7
+ } from '@purestack/ts-style'
8
+
9
+ export function registerConsentStyles() {
10
+ themes.forEach((theme) => {
11
+ registerConsentPlacementStyles(theme)
12
+ })
13
+ }
14
+
15
+ function registerConsentPlacementStyles(theme: ThemeMode) {
16
+ styleBuilder
17
+ .select('.consent', theme)
18
+ .position('fixed')
19
+ .left('0')
20
+ .right('0')
21
+ .bottom('0')
22
+ .zIndex(1200)
23
+ .pointerEvents('none')
24
+
25
+ styleBuilder
26
+ .select('.consent__banner', theme)
27
+ .pointerEvents('auto')
28
+ .margin('0 auto 1em')
29
+ .width(`min(${getBreakpoint(BREAKPOINTS.lg)}, calc(100vw - 2em))`)
30
+
31
+ styleBuilder
32
+ .select('.consent__panel', theme)
33
+ .pointerEvents('auto')
34
+ .position('fixed')
35
+ .right('0.75em')
36
+ .bottom('4rem')
37
+ .left('0.75em')
38
+ .width(`min(${getBreakpoint(BREAKPOINTS.sm)}, calc(100vw - 2em))`)
39
+ .marginLeft('auto')
40
+
41
+ styleBuilder
42
+ .select('.consent__banner[hidden], .consent__panel[hidden]', theme)
43
+ .display('none')
44
+ }