@jjlmoya/utils-civic 1.8.0 → 1.9.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 (63) hide show
  1. package/.github/workflows/ci.yml +103 -0
  2. package/.gitignore +1 -0
  3. package/package.json +17 -5
  4. package/scripts/postinstall.mjs +2 -4
  5. package/src/category/index.ts +2 -1
  6. package/src/components/ProductionBreadcrumb.astro +132 -0
  7. package/src/components/ProductionWidget.astro +182 -0
  8. package/src/entries.ts +2 -0
  9. package/src/i18n/header-ui.ts +15 -0
  10. package/src/i18n/language-ui.ts +9 -0
  11. package/src/i18n/languages.ts +24 -0
  12. package/src/identity/brands.ts +5 -0
  13. package/src/index.ts +1 -0
  14. package/src/layouts/ProductionCategoryPage.astro +184 -0
  15. package/src/layouts/ProductionPage.astro +209 -0
  16. package/src/layouts/ProductionUtilityPage.astro +235 -0
  17. package/src/mfe/assets.ts +15 -0
  18. package/src/mfe/category-ui.ts +25 -0
  19. package/src/mfe/routes.ts +50 -0
  20. package/src/pages/[locale]/[utilities]/[categories]/[category]/[slug].astro +100 -0
  21. package/src/pages/[locale]/[utilities]/[categories]/[category].astro +44 -0
  22. package/src/pages/index.astro +1 -1
  23. package/src/pages/utilidades/[slug].astro +90 -0
  24. package/src/pages/utilidades/categorias/[category].astro +38 -0
  25. package/src/tests/locale_completeness.test.ts +1 -1
  26. package/src/tests/tool_validation.test.ts +1 -1
  27. package/src/tool/access-to-information-request-builder/access-to-information-request-builder.css +577 -0
  28. package/src/tool/access-to-information-request-builder/bibliography.astro +6 -0
  29. package/src/tool/access-to-information-request-builder/bibliography.ts +6 -0
  30. package/src/tool/access-to-information-request-builder/component.astro +106 -0
  31. package/src/tool/access-to-information-request-builder/contract.test.ts +17 -0
  32. package/src/tool/access-to-information-request-builder/controller.ts +135 -0
  33. package/src/tool/access-to-information-request-builder/dom-views.ts +59 -0
  34. package/src/tool/access-to-information-request-builder/entry.ts +27 -0
  35. package/src/tool/access-to-information-request-builder/evaluator.ts +27 -0
  36. package/src/tool/access-to-information-request-builder/i18n/de.ts +52 -0
  37. package/src/tool/access-to-information-request-builder/i18n/en.ts +76 -0
  38. package/src/tool/access-to-information-request-builder/i18n/es.ts +52 -0
  39. package/src/tool/access-to-information-request-builder/i18n/fr.ts +50 -0
  40. package/src/tool/access-to-information-request-builder/i18n/id.ts +50 -0
  41. package/src/tool/access-to-information-request-builder/i18n/it.ts +50 -0
  42. package/src/tool/access-to-information-request-builder/i18n/ja.ts +50 -0
  43. package/src/tool/access-to-information-request-builder/i18n/ko.ts +50 -0
  44. package/src/tool/access-to-information-request-builder/i18n/nl.ts +49 -0
  45. package/src/tool/access-to-information-request-builder/i18n/pl.ts +49 -0
  46. package/src/tool/access-to-information-request-builder/i18n/pt.ts +49 -0
  47. package/src/tool/access-to-information-request-builder/i18n/ru.ts +49 -0
  48. package/src/tool/access-to-information-request-builder/i18n/sv.ts +49 -0
  49. package/src/tool/access-to-information-request-builder/i18n/tr.ts +49 -0
  50. package/src/tool/access-to-information-request-builder/i18n/zh.ts +49 -0
  51. package/src/tool/access-to-information-request-builder/index.ts +11 -0
  52. package/src/tool/access-to-information-request-builder/logic.test.ts +68 -0
  53. package/src/tool/access-to-information-request-builder/logic.ts +138 -0
  54. package/src/tool/access-to-information-request-builder/seo.astro +15 -0
  55. package/src/tool/access-to-information-request-builder/storage.ts +27 -0
  56. package/src/tool/access-to-information-request-builder/ui.ts +154 -0
  57. package/src/tool/access-to-information-request-builder/validation.ts +7 -0
  58. package/src/tools.ts +2 -0
  59. package/src/types.ts +2 -4
  60. package/src/worker.ts +9 -0
  61. package/tsconfig.json +14 -5
  62. package/src/pages/[locale]/[slug].astro +0 -166
  63. package/src/pages/[locale].astro +0 -253
@@ -0,0 +1,577 @@
1
+ main {
2
+ min-width: 0;
3
+ }
4
+
5
+ .access-request-builder {
6
+ --n-paper: #fffdf7;
7
+ --n-ink: #17252a;
8
+ --n-muted: #5c6c70;
9
+ --n-line: #d8dfda;
10
+ --n-accent: #b64c35;
11
+ --n-accent-soft: #f5e4dc;
12
+ --n-teal: #1d6c6a;
13
+ --n-teal-soft: #dceeea;
14
+ --n-warning: #8c5a18;
15
+ --n-warning-soft: #f9eed5;
16
+ --n-on-accent: #fff;
17
+ --n-paper-tint: #fbf3eb;
18
+
19
+
20
+ display: grid;
21
+ gap: 1.25rem;
22
+ color: var(--n-ink);
23
+ }
24
+
25
+ .theme-dark .access-request-builder {
26
+ --n-paper: #1c2425;
27
+ --n-ink: #f4f0e6;
28
+ --n-muted: #b4c1bc;
29
+ --n-line: #40504e;
30
+ --n-accent: #f08262;
31
+ --n-accent-soft: #482c27;
32
+ --n-teal: #75d1c4;
33
+ --n-teal-soft: #203d3a;
34
+ --n-warning: #f2c56d;
35
+ --n-warning-soft: #46351d;
36
+ --n-on-accent: #17252a;
37
+ --n-paper-tint: #2b2825;
38
+ }
39
+
40
+ .request-workbench,
41
+ .request-result,
42
+ .request-guidance {
43
+ background: var(--n-paper);
44
+ border: 1px solid var(--n-line);
45
+ border-radius: 1.25rem;
46
+ }
47
+
48
+ .request-workbench {
49
+ overflow: visible;
50
+ box-shadow: 0 1rem 2rem rgba(23, 37, 42, 0.08);
51
+ }
52
+
53
+ .request-intro {
54
+ display: flex;
55
+ justify-content: space-between;
56
+ gap: 2rem;
57
+ padding: 2rem 2.25rem 1.5rem;
58
+ border-bottom: 1px solid var(--n-line);
59
+ }
60
+
61
+ .request-intro h2 {
62
+ max-width: 30rem;
63
+ margin: .35rem 0 0;
64
+ font-size: clamp(1.5rem, 3vw, 2.35rem);
65
+ line-height: 1.05;
66
+ letter-spacing: -.04em;
67
+ }
68
+
69
+ .request-intro p {
70
+ max-width: 20rem;
71
+ margin: 1.4rem 0 0;
72
+ color: var(--n-muted);
73
+ line-height: 1.55;
74
+ }
75
+
76
+ .eyebrow {
77
+ color: var(--n-accent);
78
+ font-size: .7rem;
79
+ font-weight: 800;
80
+ letter-spacing: .16em;
81
+ text-transform: uppercase;
82
+ }
83
+
84
+ .preset-row {
85
+ display: flex;
86
+ flex-wrap: wrap;
87
+ align-items: center;
88
+ gap: .55rem;
89
+ padding: .9rem 2.25rem;
90
+ background: var(--n-accent-soft);
91
+ color: var(--n-accent);
92
+ font-size: .82rem;
93
+ font-weight: 700;
94
+ }
95
+
96
+ .preset-row button,
97
+ .text-action,
98
+ .secondary-action,
99
+ .output-actions button {
100
+ border: 1px solid var(--n-line);
101
+ border-radius: 999px;
102
+ padding: .55rem .8rem;
103
+ background: var(--n-paper);
104
+ color: var(--n-ink);
105
+ cursor: pointer;
106
+ font: inherit;
107
+ }
108
+
109
+ .preset-row button:hover,
110
+ .text-action:hover,
111
+ .secondary-action:hover,
112
+ .output-actions button:hover {
113
+ border-color: var(--n-accent);
114
+ color: var(--n-accent);
115
+ }
116
+
117
+ .request-flow {
118
+ display: grid;
119
+ grid-template-columns: repeat(3, 1fr);
120
+ }
121
+
122
+ .flow-section {
123
+ min-width: 0;
124
+ padding: 1.5rem 1.5rem 1.75rem;
125
+ border-right: 1px solid var(--n-line);
126
+ }
127
+
128
+ .flow-section:last-child {
129
+ border-right: 0;
130
+ }
131
+
132
+ .section-marker {
133
+ color: var(--n-accent);
134
+ font-size: .75rem;
135
+ font-weight: 800;
136
+ letter-spacing: .1em;
137
+ }
138
+
139
+ .section-heading {
140
+ display: flex;
141
+ align-items: baseline;
142
+ justify-content: space-between;
143
+ gap: .75rem;
144
+ margin: .45rem 0 1.2rem;
145
+ }
146
+
147
+ .section-heading h3 {
148
+ margin: 0;
149
+ font-size: 1.05rem;
150
+ }
151
+
152
+ .section-heading span {
153
+ color: var(--n-muted);
154
+ font-size: .72rem;
155
+ }
156
+
157
+ .section-content > label,
158
+ .field-label,
159
+ .period-fieldset legend,
160
+ .request-notes > label {
161
+ display: block;
162
+ margin: .9rem 0 .35rem;
163
+ color: var(--n-muted);
164
+ font-size: .75rem;
165
+ font-weight: 750;
166
+ }
167
+
168
+ .access-request-builder input[type="text"],
169
+ .access-request-builder input[type="date"],
170
+ .access-request-builder textarea,
171
+ .format-trigger {
172
+ width: 100%;
173
+ border: 1px solid var(--n-line);
174
+ border-radius: .55rem;
175
+ padding: .7rem .75rem;
176
+ background: transparent;
177
+ color: var(--n-ink);
178
+ font: inherit;
179
+ line-height: 1.35;
180
+ }
181
+
182
+ .access-request-builder textarea {
183
+ resize: vertical;
184
+ }
185
+ .access-request-builder input:focus,
186
+ .access-request-builder textarea:focus,
187
+ .format-trigger:focus {
188
+ outline: 2px solid var(--n-teal);
189
+ outline-offset: 2px;
190
+ }
191
+
192
+ .request-line + .request-line {
193
+ margin-top: 1rem;
194
+ }
195
+
196
+ .request-line label {
197
+ display: block;
198
+ margin-bottom: .35rem;
199
+ color: var(--n-muted);
200
+ font-size: .75rem;
201
+ font-weight: 750;
202
+ }
203
+
204
+ .request-line button {
205
+ margin-top: .35rem;
206
+ border: 0;
207
+ background: transparent;
208
+ color: var(--n-accent);
209
+ cursor: pointer;
210
+ font: inherit;
211
+ font-size: .75rem;
212
+ }
213
+
214
+ .text-action {
215
+ margin-top: 1rem;
216
+ border: 0;
217
+ padding-left: 0;
218
+ background: transparent;
219
+ color: var(--n-teal);
220
+ font-size: .82rem;
221
+ font-weight: 750;
222
+ }
223
+
224
+ .period-fieldset {
225
+ margin: 0;
226
+ padding: 0;
227
+ border: 0;
228
+ }
229
+
230
+ .date-grid {
231
+ display: grid;
232
+ grid-template-columns: 1fr 1fr;
233
+ gap: .6rem;
234
+ }
235
+
236
+ .date-grid label {
237
+ color: var(--n-muted);
238
+ font-size: .72rem;
239
+ }
240
+
241
+ .date-grid input {
242
+ margin-top: .35rem;
243
+ }
244
+
245
+ .format-picker {
246
+ position: relative;
247
+ }
248
+
249
+ .format-trigger {
250
+ display: flex;
251
+ justify-content: space-between;
252
+ text-align: left;
253
+ cursor: pointer;
254
+ }
255
+
256
+ .format-trigger::after {
257
+ content: '⌄';
258
+ color: var(--n-accent);
259
+ }
260
+
261
+ .format-menu {
262
+ position: absolute;
263
+ z-index: 3;
264
+ top: 100%;
265
+ right: 0;
266
+ left: 0;
267
+ display: grid;
268
+ margin-top: .3rem;
269
+ border: 1px solid var(--n-line);
270
+ border-radius: .55rem;
271
+ background: var(--n-paper);
272
+ box-shadow: 0 .8rem 1.5rem rgba(23, 37, 42, 0.15);
273
+ }
274
+
275
+ .format-menu[hidden] {
276
+ display: none;
277
+ }
278
+
279
+ .format-menu button {
280
+ border: 0;
281
+ border-bottom: 1px solid var(--n-line);
282
+ padding: .65rem .75rem;
283
+ background: transparent;
284
+ color: var(--n-ink);
285
+ text-align: left;
286
+ cursor: pointer;
287
+ font: inherit;
288
+ font-size: .82rem;
289
+ }
290
+
291
+ .format-menu button:last-child {
292
+ border-bottom: 0;
293
+ }
294
+ .format-menu button:hover,
295
+ .format-menu button[aria-selected="true"] {
296
+ background: var(--n-teal-soft);
297
+ color: var(--n-teal);
298
+ }
299
+
300
+ .request-notes {
301
+ display: grid;
302
+ grid-template-columns: 1fr 1fr;
303
+ gap: 1rem;
304
+ padding: 1.25rem 2.25rem;
305
+ border-top: 1px solid var(--n-line);
306
+ }
307
+
308
+ .request-notes > label {
309
+ margin: 0;
310
+ }
311
+
312
+ .request-notes .check-row {
313
+ display: flex;
314
+ align-items: center;
315
+ gap: .5rem;
316
+ color: var(--n-ink);
317
+ font-size: .78rem;
318
+ font-weight: 500;
319
+ }
320
+
321
+ .check-row input {
322
+ accent-color: var(--n-teal);
323
+ }
324
+
325
+ .request-actions {
326
+ display: flex;
327
+ gap: .7rem;
328
+ padding: 0 2.25rem 1.75rem;
329
+ }
330
+
331
+ .primary-action {
332
+ border: 0;
333
+ border-radius: .55rem;
334
+ padding: .8rem 1rem;
335
+ background: var(--n-teal);
336
+ color: var(--n-on-accent);
337
+ cursor: pointer;
338
+ font: inherit;
339
+ font-weight: 800;
340
+ }
341
+
342
+ .secondary-action {
343
+ border-radius: .55rem;
344
+ }
345
+
346
+ .request-result {
347
+ overflow: hidden;
348
+ }
349
+
350
+ .request-output {
351
+ padding: 1.5rem 2rem;
352
+ }
353
+
354
+ .output-heading {
355
+ display: flex;
356
+ justify-content: space-between;
357
+ gap: 1.5rem;
358
+ align-items: start;
359
+ }
360
+
361
+ .output-heading p {
362
+ max-width: 38rem;
363
+ margin: .4rem 0 0;
364
+ color: var(--n-muted);
365
+ font-size: .86rem;
366
+ line-height: 1.45;
367
+ }
368
+
369
+ .output-actions {
370
+ display: flex;
371
+ flex-wrap: wrap;
372
+ gap: .5rem;
373
+ justify-content: end;
374
+ }
375
+
376
+ .output-actions button {
377
+ border-radius: .45rem;
378
+ font-size: .78rem;
379
+ }
380
+
381
+ .output-actions button:disabled {
382
+ cursor: not-allowed;
383
+ opacity: .45;
384
+ }
385
+
386
+ .request-markdown {
387
+ display: block;
388
+ margin: 1.25rem 0 0;
389
+ padding: 1.25rem;
390
+ overflow-x: auto;
391
+ min-height: 22rem;
392
+ border-left: 3px solid var(--n-accent);
393
+ background: var(--n-paper-tint);
394
+ color: var(--n-ink);
395
+ font: .86rem/1.65 ui-monospace, SFMono-Regular, Consolas, monospace;
396
+ white-space: pre-wrap;
397
+ resize: vertical;
398
+ }
399
+
400
+ .request-empty {
401
+ margin: 1.25rem 0 0;
402
+ padding: 1.25rem;
403
+ border: 1px dashed var(--n-line);
404
+ color: var(--n-muted);
405
+ }
406
+
407
+ .copy-confirmation {
408
+ margin: .7rem 0 0;
409
+ color: var(--n-teal);
410
+ font-size: .8rem;
411
+ font-weight: 700;
412
+ }
413
+
414
+ .trace-strip {
415
+ display: grid;
416
+ grid-template-columns: 1fr 2fr;
417
+ gap: 1.5rem;
418
+ padding: 1.35rem 2rem;
419
+ border-top: 1px solid var(--n-line);
420
+ background: var(--n-teal-soft);
421
+ }
422
+
423
+ .trace-heading {
424
+ display: grid;
425
+ align-content: start;
426
+ gap: .45rem;
427
+ }
428
+
429
+ .trace-heading strong {
430
+ font-size: .88rem;
431
+ line-height: 1.35;
432
+ }
433
+
434
+ .trail-checklist {
435
+ display: grid;
436
+ grid-template-columns: repeat(2, 1fr);
437
+ gap: .55rem;
438
+ margin: 0;
439
+ padding: 0;
440
+ list-style: none;
441
+ }
442
+
443
+ .trail-check {
444
+ display: grid;
445
+ grid-template-columns: 1.2rem 1fr;
446
+ align-items: center;
447
+ gap: .35rem;
448
+ color: var(--n-ink);
449
+ font-size: .76rem;
450
+ }
451
+
452
+ .trail-check > span:first-child {
453
+ display: grid;
454
+ place-items: center;
455
+ width: 1.1rem;
456
+ height: 1.1rem;
457
+ border-radius: 50%;
458
+ background: var(--n-paper);
459
+ color: var(--n-muted);
460
+ font-weight: 800;
461
+ }
462
+
463
+ .trail-check-complete > span:first-child,
464
+ .trail-check-caution > span:first-child {
465
+ color: var(--n-on-accent);
466
+ }
467
+
468
+ .trail-check-complete > span:first-child {
469
+ background: var(--n-teal);
470
+ }
471
+
472
+ .trail-check-caution > span:first-child {
473
+ background: var(--n-warning);
474
+ }
475
+
476
+ .trail-check strong {
477
+ grid-column: 2;
478
+ color: var(--n-muted);
479
+ font-size: .65rem;
480
+ text-transform: uppercase;
481
+ }
482
+
483
+ .trail-warnings {
484
+ grid-column: 1 / -1;
485
+ border-top: 1px solid var(--n-line);
486
+ padding-top: .8rem;
487
+ }
488
+
489
+ .trail-warnings-title {
490
+ margin: 0 0 .35rem;
491
+ color: var(--n-warning);
492
+ font-size: .75rem;
493
+ font-weight: 800;
494
+ }
495
+
496
+ .trail-warnings ul {
497
+ display: flex;
498
+ flex-wrap: wrap;
499
+ gap: .45rem 1.2rem;
500
+ margin: 0;
501
+ padding-left: 1rem;
502
+ color: var(--n-ink);
503
+ font-size: .75rem;
504
+ }
505
+
506
+ .request-guidance {
507
+ display: grid;
508
+ grid-template-columns: repeat(3, 1fr);
509
+ }
510
+
511
+ .request-guidance section {
512
+ padding: 1.25rem 1.5rem;
513
+ border-right: 1px solid var(--n-line);
514
+ }
515
+
516
+ .request-guidance section:last-child {
517
+ border-right: 0;
518
+ }
519
+
520
+ .request-guidance h2 {
521
+ margin: 0 0 .55rem;
522
+ font-size: 1rem;
523
+ }
524
+
525
+ .request-guidance p {
526
+ margin: 0;
527
+ color: var(--n-muted);
528
+ font-size: .82rem;
529
+ line-height: 1.55;
530
+ }
531
+
532
+ @media (max-width: 850px) {
533
+ .request-flow,
534
+ .request-guidance { grid-template-columns: 1fr; }
535
+ .flow-section,
536
+ .request-guidance section {
537
+ border-right: 0;
538
+ border-bottom: 1px solid var(--n-line);
539
+ }
540
+ .flow-section:last-child,
541
+ .request-guidance section:last-child { border-bottom: 0; }
542
+ .request-intro,
543
+ .output-heading { display: grid; }
544
+ .output-actions { justify-content: start; }
545
+ .trace-strip { grid-template-columns: 1fr; }
546
+ }
547
+
548
+ @media (max-width: 560px) {
549
+ .request-intro,
550
+ .preset-row,
551
+ .request-notes,
552
+ .request-actions,
553
+ .request-output,
554
+ .trace-strip {
555
+ padding-right: 1rem;
556
+ padding-left: 1rem;
557
+ }
558
+ .request-notes { grid-template-columns: 1fr; }
559
+ .trail-checklist { grid-template-columns: 1fr; }
560
+ }
561
+
562
+ @media print {
563
+ .request-workbench,
564
+ .request-guidance,
565
+ .trace-strip,
566
+ .output-actions,
567
+ .copy-confirmation { display: none; }
568
+ .request-result {
569
+ border: 0;
570
+ box-shadow: none;
571
+ }
572
+
573
+ .request-markdown {
574
+ border: 0;
575
+ background: transparent;
576
+ }
577
+ }
@@ -0,0 +1,6 @@
1
+ ---
2
+ import { Bibliography } from '@jjlmoya/utils-shared';
3
+ import { bibliography } from './bibliography';
4
+ ---
5
+
6
+ <Bibliography links={bibliography} />
@@ -0,0 +1,6 @@
1
+ import type { BibliographyEntry } from '../../types';
2
+
3
+ export const bibliography: BibliographyEntry[] = [
4
+ { name: 'Information Commissioner\'s Office, How to write an effective request for information', url: 'https://ico.org.uk/for-the-public/official-information/preparing-and-submitting-your-information-request/' },
5
+ { name: 'CNIL, Droit d\'accès aux documents administratifs', url: 'https://www.cnil.fr/fr/definition/droit-dacces-aux-documents-administratifs' },
6
+ ];
@@ -0,0 +1,106 @@
1
+ ---
2
+ import type { AccessRequestUI } from './ui';
3
+
4
+ interface Props {
5
+ ui: AccessRequestUI;
6
+ }
7
+
8
+ const { ui } = Astro.props as Props;
9
+ ---
10
+
11
+ <div class="access-request-builder" data-access-request-builder>
12
+ <div class="request-workbench">
13
+ <div class="request-intro">
14
+ <div>
15
+ <span class="eyebrow">{ui.eyebrow}</span>
16
+ <h2>{ui.introHeading}</h2>
17
+ </div>
18
+ <p>{ui.introText}</p>
19
+ </div>
20
+
21
+ <div class="preset-row" aria-label={ui.presetLabel}>
22
+ <span>{ui.presetLabel}</span>
23
+ <button type="button" data-preset="records">{ui.presetRecords}</button>
24
+ <button type="button" data-preset="spending">{ui.presetSpending}</button>
25
+ <button type="button" data-preset="meetings">{ui.presetMeetings}</button>
26
+ </div>
27
+
28
+ <div class="request-flow">
29
+ <section class="flow-section flow-target">
30
+ <div class="section-marker">01</div>
31
+ <div class="section-content">
32
+ <div class="section-heading"><h3>{ui.targetHeading}</h3><span>{ui.targetPrompt}</span></div>
33
+ <label for="request-recipient">{ui.recipientLabel}</label>
34
+ <input id="request-recipient" data-field="recipient" type="text" placeholder={ui.recipientPlaceholder} />
35
+ <label for="request-subject">{ui.subjectLabel}</label>
36
+ <input id="request-subject" data-field="subject" type="text" placeholder={ui.subjectPlaceholder} />
37
+ </div>
38
+ </section>
39
+
40
+ <section class="flow-section flow-question">
41
+ <div class="section-marker">02</div>
42
+ <div class="section-content">
43
+ <div class="section-heading"><h3>{ui.itemsLabel}</h3><span>{ui.questionPrompt}</span></div>
44
+ <div data-request-lines class="request-lines">
45
+ <div class="request-line">
46
+ <label for="request-item-0">{ui.itemNumber} 1</label>
47
+ <textarea id="request-item-0" data-item-index="0" rows="3" placeholder={ui.itemPlaceholder}></textarea>
48
+ </div>
49
+ </div>
50
+ <button class="text-action" type="button" data-add-item>+ {ui.addItem}</button>
51
+ </div>
52
+ </section>
53
+
54
+ <section class="flow-section flow-boundary">
55
+ <div class="section-marker">03</div>
56
+ <div class="section-content">
57
+ <div class="section-heading"><h3>{ui.boundaryHeading}</h3><span>{ui.boundaryPrompt}</span></div>
58
+ <fieldset class="period-fieldset">
59
+ <legend>{ui.periodLabel}</legend>
60
+ <div class="date-grid"><label for="request-period-from">{ui.periodFrom}<input id="request-period-from" data-field="periodFrom" type="date" /></label><label for="request-period-to">{ui.periodTo}<input id="request-period-to" data-field="periodTo" type="date" /></label></div>
61
+ </fieldset>
62
+ <label for="request-geography">{ui.geographyLabel}</label>
63
+ <input id="request-geography" data-field="geography" type="text" placeholder={ui.geographyPlaceholder} />
64
+ <div class="format-picker" data-format-picker>
65
+ <span class="field-label">{ui.formatLabel}</span>
66
+ <button type="button" class="format-trigger" data-format-trigger aria-expanded="false">{ui.formatPlaceholder}</button>
67
+ <div class="format-menu" data-format-menu hidden role="listbox">
68
+ <button type="button" data-format-option={ui.formatEmail} aria-selected="false" role="option">{ui.formatEmail}</button>
69
+ <button type="button" data-format-option={ui.formatCsv} aria-selected="false" role="option">{ui.formatCsv}</button>
70
+ <button type="button" data-format-option={ui.formatPdf} aria-selected="false" role="option">{ui.formatPdf}</button>
71
+ <button type="button" data-format-option={ui.formatOriginal} aria-selected="false" role="option">{ui.formatOriginal}</button>
72
+ </div>
73
+ </div>
74
+ <label for="request-delivery">{ui.deliveryLabel}</label>
75
+ <input id="request-delivery" data-field="delivery" type="text" placeholder={ui.deliveryPlaceholder} />
76
+ </div>
77
+ </section>
78
+ </div>
79
+
80
+ <div class="request-notes">
81
+ <label for="request-attachments">{ui.attachmentsLabel}<textarea id="request-attachments" data-field="attachments" rows="2" placeholder={ui.attachmentsPlaceholder}></textarea></label>
82
+ <label for="request-contact">{ui.contactLabel}<textarea id="request-contact" data-field="contact" rows="2" placeholder={ui.contactPlaceholder}></textarea></label>
83
+ <label class="check-row"><input type="checkbox" data-field="noAttachments" /> <span>{ui.noAttachments}</span></label>
84
+ <label class="check-row"><input type="checkbox" data-field="savedCopy" /> <span>{ui.savedCopy}</span></label>
85
+ </div>
86
+
87
+ <div class="request-actions"><button class="primary-action" type="button" data-generate>{ui.generateAction}</button><button class="secondary-action" type="button" data-reset>{ui.resetAction}</button></div>
88
+ </div>
89
+
90
+ <div class="request-result" data-request-result aria-live="polite"></div>
91
+
92
+ <div class="request-guidance">
93
+ <section><h2>{ui.methodHeading}</h2><p>{ui.methodText}</p></section>
94
+ <section><h2>{ui.limitsHeading}</h2><p>{ui.limitsText}</p></section>
95
+ <section><h2>{ui.edgeCasesHeading}</h2><p>{ui.edgeCasesText}</p></section>
96
+ </div>
97
+ </div>
98
+
99
+ <script is:inline type="application/json" data-access-request-ui set:html={JSON.stringify(ui)}></script>
100
+
101
+ <script>
102
+ import { mountAccessRequestBuilder } from './controller';
103
+ const root = document.querySelector<HTMLElement>('[data-access-request-builder]');
104
+ const data = document.querySelector<HTMLScriptElement>('[data-access-request-ui]');
105
+ if (root && data) mountAccessRequestBuilder(root, JSON.parse(data.textContent || '{}'));
106
+ </script>
@@ -0,0 +1,17 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { accessToInformationRequestBuilder } from './entry';
3
+ import { ACCESS_TO_INFORMATION_REQUEST_BUILDER_TOOL } from './index';
4
+
5
+ describe('access to information request builder runtime contract', () => {
6
+ it('exposes all locale loaders and page component loaders', async () => {
7
+ const locales = ['de', 'en', 'es', 'fr', 'id', 'it', 'ja', 'ko', 'nl', 'pl', 'pt', 'ru', 'sv', 'tr', 'zh'] as const;
8
+ for (const locale of locales) {
9
+ const content = await accessToInformationRequestBuilder.i18n[locale]?.();
10
+ expect(content?.ui).toBeDefined();
11
+ expect(content?.schemas?.length).toBe(3);
12
+ }
13
+ expect(typeof ACCESS_TO_INFORMATION_REQUEST_BUILDER_TOOL.Component).toBe('function');
14
+ expect(typeof ACCESS_TO_INFORMATION_REQUEST_BUILDER_TOOL.SEOComponent).toBe('function');
15
+ expect(typeof ACCESS_TO_INFORMATION_REQUEST_BUILDER_TOOL.BibliographyComponent).toBe('function');
16
+ });
17
+ });