@ks-digital/designsystem-angular 0.0.1-alpha.23 → 0.0.1-alpha.25

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 (109) hide show
  1. package/.storybook/customTheme.ts +15 -0
  2. package/.storybook/default-args.ts +18 -0
  3. package/.storybook/main.ts +27 -0
  4. package/.storybook/manager.ts +10 -0
  5. package/.storybook/preview-head.html +16 -0
  6. package/.storybook/preview.ts +70 -0
  7. package/.storybook/themes.ts +9 -0
  8. package/.storybook/tsconfig.json +16 -0
  9. package/.storybook/vite.config.mts +5 -0
  10. package/README.md +3 -3
  11. package/eslint.config.mjs +28 -0
  12. package/ng-package.json +9 -0
  13. package/package.json +18 -27
  14. package/project.json +81 -0
  15. package/src/components/alert/alert.mdx +46 -0
  16. package/src/components/alert/alert.spec.ts +33 -0
  17. package/src/components/alert/alert.stories.ts +138 -0
  18. package/src/components/alert/alert.ts +46 -0
  19. package/src/components/alert/index.ts +1 -0
  20. package/src/components/button/button.mdx +40 -0
  21. package/src/components/button/button.spec.ts +86 -0
  22. package/src/components/button/button.stories.ts +123 -0
  23. package/src/components/button/button.ts +60 -0
  24. package/src/components/button/index.ts +1 -0
  25. package/src/components/card/card-block.ts +10 -0
  26. package/src/components/card/card.mdx +100 -0
  27. package/src/components/card/card.spec.ts +70 -0
  28. package/src/components/card/card.stories.ts +101 -0
  29. package/src/components/card/card.ts +44 -0
  30. package/src/components/card/index.ts +2 -0
  31. package/src/components/checkbox/README.md +13 -0
  32. package/src/components/checkbox/checkbox.mdx +50 -0
  33. package/src/components/checkbox/checkbox.spec.ts +21 -0
  34. package/src/components/checkbox/checkbox.stories.ts +182 -0
  35. package/src/components/checkbox/index.ts +0 -0
  36. package/src/components/colors.ts +36 -0
  37. package/src/components/common-inputs.ts +30 -0
  38. package/src/components/details/controlled-details.ts +63 -0
  39. package/src/components/details/details-content.ts +7 -0
  40. package/src/components/details/details-summary.ts +7 -0
  41. package/src/components/details/details.mdx +89 -0
  42. package/src/components/details/details.spec.ts +56 -0
  43. package/src/components/details/details.stories.ts +129 -0
  44. package/src/components/details/details.ts +69 -0
  45. package/src/components/details/index.ts +3 -0
  46. package/src/components/field/field-counter.ts +56 -0
  47. package/src/components/field/field-description.ts +10 -0
  48. package/src/components/field/field-error.ts +13 -0
  49. package/src/components/field/field-observer.ts +121 -0
  50. package/src/components/field/field-state.ts +21 -0
  51. package/src/components/field/field.mdx +40 -0
  52. package/src/components/field/field.spec.ts +131 -0
  53. package/src/components/field/field.stories.ts +98 -0
  54. package/src/components/field/field.ts +70 -0
  55. package/src/components/field/index.ts +3 -0
  56. package/src/components/fieldset/fieldset-description.ts +8 -0
  57. package/src/components/fieldset/fieldset-legend.ts +11 -0
  58. package/src/components/fieldset/fieldset.spec.ts +80 -0
  59. package/src/components/fieldset/fieldset.ts +11 -0
  60. package/src/components/fieldset/index.ts +3 -0
  61. package/src/components/input/index.ts +1 -0
  62. package/src/components/input/input.mdx +11 -0
  63. package/src/components/input/input.spec.ts +25 -0
  64. package/src/components/input/input.stories.ts +72 -0
  65. package/src/components/input/input.ts +67 -0
  66. package/src/components/label/index.ts +1 -0
  67. package/src/components/label/label.ts +17 -0
  68. package/src/components/paragraph/index.ts +1 -0
  69. package/src/components/paragraph/paragraph.ts +10 -0
  70. package/src/components/popover/controlled-popover.ts +62 -0
  71. package/src/components/popover/index.ts +1 -0
  72. package/src/components/popover/popover.mdx +81 -0
  73. package/src/components/popover/popover.spec.ts +143 -0
  74. package/src/components/popover/popover.stories.ts +63 -0
  75. package/src/components/popover/popover.ts +186 -0
  76. package/src/components/radio/radio.mdx +117 -0
  77. package/src/components/radio/radio.stories.ts +226 -0
  78. package/src/components/search/index.ts +4 -0
  79. package/src/components/search/search-button.ts +35 -0
  80. package/src/components/search/search-clear.ts +57 -0
  81. package/src/components/search/search-input.ts +18 -0
  82. package/src/components/search/search.mdx +56 -0
  83. package/src/components/search/search.spec.ts +48 -0
  84. package/src/components/search/search.stories.ts +205 -0
  85. package/src/components/search/search.ts +50 -0
  86. package/src/components/spinner/index.ts +1 -0
  87. package/src/components/spinner/spinner.mdx +24 -0
  88. package/src/components/spinner/spinner.spec.ts +13 -0
  89. package/src/components/spinner/spinner.stories.ts +54 -0
  90. package/src/components/spinner/spinner.ts +62 -0
  91. package/src/components/switch/switch.mdx +82 -0
  92. package/src/components/switch/switch.stories.ts +94 -0
  93. package/src/components/textarea/textarea.mdx +14 -0
  94. package/src/components/textarea/textarea.stories.ts +52 -0
  95. package/src/components/validation-message/index.ts +1 -0
  96. package/src/components/validation-message/validation-message.ts +11 -0
  97. package/src/index.ts +14 -0
  98. package/src/test-setup.ts +12 -0
  99. package/src/utils/log-if-devmode.ts +13 -0
  100. package/src/utils/random-id.ts +3 -0
  101. package/tsconfig.json +34 -0
  102. package/tsconfig.lib.json +28 -0
  103. package/tsconfig.lib.prod.json +9 -0
  104. package/tsconfig.spec.json +30 -0
  105. package/vite.config.mts +35 -0
  106. package/dist/README.md +0 -55
  107. package/dist/fesm2022/ks-digital-designsystem-angular.mjs +0 -1068
  108. package/dist/fesm2022/ks-digital-designsystem-angular.mjs.map +0 -1
  109. package/dist/index.d.ts +0 -315
@@ -1,1068 +0,0 @@
1
- import * as i0 from '@angular/core';
2
- import { input, Directive, booleanAttribute, Component, inject, ElementRef, output, viewChild, CUSTOM_ELEMENTS_SCHEMA, signal, computed, Injectable, numberAttribute, effect, contentChild, contentChildren, afterNextRender, isDevMode } from '@angular/core';
3
- import '@u-elements/u-details';
4
- import { autoUpdate, computePosition, offset, flip, shift } from '@floating-ui/dom';
5
-
6
- /* eslint-disable @angular-eslint/no-input-rename */
7
- /**
8
- * We use input aliasing to bridge the gap between Angular's camelCase property naming convention and our HTML data attributes.
9
- * This approach allows us to use valid HTML data attributes as documented by Designsystemet while maintaining
10
- * proper TypeScript intellisense support.
11
- */
12
- class CommonInputs {
13
- /**
14
- * Changes size for descendant Designsystemet components. Select from predefined sizes.
15
- * @attribute data-size
16
- */
17
- dataSize = input(undefined, { alias: 'data-size' });
18
- /**
19
- * Changes color for descendant Designsystemet components.
20
- * Select from predefined colors and colors defined using theme.designsystemet.no.
21
- * @attribute data-color
22
- */
23
- dataColor = input(undefined, { alias: 'data-color' });
24
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.4", ngImport: i0, type: CommonInputs, deps: [], target: i0.ɵɵFactoryTarget.Directive });
25
- static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "20.0.4", type: CommonInputs, isStandalone: true, inputs: { dataSize: { classPropertyName: "dataSize", publicName: "data-size", isSignal: true, isRequired: false, transformFunction: null }, dataColor: { classPropertyName: "dataColor", publicName: "data-color", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0 });
26
- }
27
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.4", ngImport: i0, type: CommonInputs, decorators: [{
28
- type: Directive
29
- }] });
30
-
31
- /* eslint-disable @angular-eslint/no-input-rename */
32
- class Spinner {
33
- /**
34
- * Aria-label for the spinner
35
- */
36
- ariaLabel = input(undefined, { alias: 'aria-label' });
37
- /**
38
- * Aria-label for the spinner
39
- */
40
- dataSize = input(undefined, { alias: 'data-size' });
41
- /**
42
- * Aria-label for the spinner
43
- */
44
- dataColor = input(undefined, { alias: 'data-color' });
45
- /**
46
- * Aria-hidden for the spinner
47
- */
48
- ariaHidden = input(undefined, {
49
- transform: booleanAttribute,
50
- alias: 'aria-hidden',
51
- });
52
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.4", ngImport: i0, type: Spinner, deps: [], target: i0.ɵɵFactoryTarget.Component });
53
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "20.0.4", type: Spinner, isStandalone: true, selector: "ksd-spinner", inputs: { ariaLabel: { classPropertyName: "ariaLabel", publicName: "aria-label", isSignal: true, isRequired: false, transformFunction: null }, dataSize: { classPropertyName: "dataSize", publicName: "data-size", isSignal: true, isRequired: false, transformFunction: null }, dataColor: { classPropertyName: "dataColor", publicName: "data-color", isSignal: true, isRequired: false, transformFunction: null }, ariaHidden: { classPropertyName: "ariaHidden", publicName: "aria-hidden", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
54
- <svg
55
- class="ds-spinner"
56
- role="img"
57
- viewBox="0 0 50 50"
58
- [attr.data-size]="dataSize()"
59
- [attr.data-color]="dataColor()"
60
- >
61
- <circle
62
- class="ds-spinner__background"
63
- cx="25"
64
- cy="25"
65
- r="20"
66
- fill="none"
67
- stroke-width="5"
68
- />
69
- <circle
70
- class="ds-spinner__circle"
71
- cx="25"
72
- cy="25"
73
- r="20"
74
- fill="none"
75
- stroke-width="5"
76
- />
77
- </svg>
78
- `, isInline: true, styles: [":host{display:contents}\n"] });
79
- }
80
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.4", ngImport: i0, type: Spinner, decorators: [{
81
- type: Component,
82
- args: [{ selector: 'ksd-spinner', template: `
83
- <svg
84
- class="ds-spinner"
85
- role="img"
86
- viewBox="0 0 50 50"
87
- [attr.data-size]="dataSize()"
88
- [attr.data-color]="dataColor()"
89
- >
90
- <circle
91
- class="ds-spinner__background"
92
- cx="25"
93
- cy="25"
94
- r="20"
95
- fill="none"
96
- stroke-width="5"
97
- />
98
- <circle
99
- class="ds-spinner__circle"
100
- cx="25"
101
- cy="25"
102
- r="20"
103
- fill="none"
104
- stroke-width="5"
105
- />
106
- </svg>
107
- `, styles: [":host{display:contents}\n"] }]
108
- }] });
109
-
110
- class Button {
111
- /**
112
- * Specify which variant to use
113
- * @default 'primary'
114
- */
115
- variant = input('primary');
116
- /**
117
- * Toggle loading state.
118
- * Pass an element if you want to display a custom loader.
119
- *
120
- * @default false
121
- */
122
- loading = input(false, { transform: booleanAttribute });
123
- /**
124
- * Disables element
125
- */
126
- disabled = input(false, { transform: booleanAttribute });
127
- /**
128
- * If this is a button with only an icon
129
- */
130
- icon = input(false, { transform: booleanAttribute });
131
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.4", ngImport: i0, type: Button, deps: [], target: i0.ɵɵFactoryTarget.Component });
132
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.0.4", type: Button, isStandalone: true, selector: "button[ksd-button], a[ksd-button]", inputs: { variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, icon: { classPropertyName: "icon", publicName: "icon", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "type": "button" }, properties: { "attr.data-variant": "variant()", "attr.data-icon": "icon() || null", "attr.disabled": "disabled() ? true : null", "attr.aria-busy": "loading() ? true : null" }, classAttribute: "ds-button" }, hostDirectives: [{ directive: CommonInputs, inputs: ["data-size", "data-size", "data-color", "data-color"] }], ngImport: i0, template: `
133
- @if (loading()) {
134
- <ksd-spinner aria-hidden="true" />
135
- }
136
- <ng-content />
137
- `, isInline: true, styles: [":host ::ng-deep>*{display:inline-flex}\n"], dependencies: [{ kind: "component", type: Spinner, selector: "ksd-spinner", inputs: ["aria-label", "data-size", "data-color", "aria-hidden"] }] });
138
- }
139
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.4", ngImport: i0, type: Button, decorators: [{
140
- type: Component,
141
- args: [{ selector: 'button[ksd-button], a[ksd-button]', hostDirectives: [
142
- {
143
- directive: CommonInputs,
144
- inputs: ['data-size', 'data-color'],
145
- },
146
- ], imports: [Spinner], host: {
147
- class: 'ds-button',
148
- type: 'button',
149
- '[attr.data-variant]': 'variant()',
150
- '[attr.data-icon]': 'icon() || null',
151
- '[attr.disabled]': 'disabled() ? true : null',
152
- '[attr.aria-busy]': 'loading() ? true : null',
153
- }, template: `
154
- @if (loading()) {
155
- <ksd-spinner aria-hidden="true" />
156
- }
157
- <ng-content />
158
- `, styles: [":host ::ng-deep>*{display:inline-flex}\n"] }]
159
- }] });
160
-
161
- class Card {
162
- /**
163
- * Change the background color of the card
164
- * @default 'default'
165
- */
166
- variant = input('default');
167
- elementRef = inject(ElementRef);
168
- projectedLink() {
169
- const el = this.elementRef.nativeElement;
170
- return el?.querySelector('h1 a, h2 a, h3 a, h4 a, h5 a, h6 a');
171
- }
172
- handleClick = (event) => {
173
- const link = this.projectedLink();
174
- if (!link)
175
- return;
176
- if (event.metaKey || event.ctrlKey) {
177
- window.open(link.href, '_blank', 'noopener,noreferrer');
178
- }
179
- else {
180
- link.click();
181
- }
182
- };
183
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.4", ngImport: i0, type: Card, deps: [], target: i0.ɵɵFactoryTarget.Component });
184
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "20.0.4", type: Card, isStandalone: true, selector: "[ksd-card]", inputs: { variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null } }, host: { listeners: { "click": "handleClick($event)" }, properties: { "attr.data-variant": "variant()" }, classAttribute: "ds-card" }, hostDirectives: [{ directive: CommonInputs, inputs: ["data-size", "data-size", "data-color", "data-color"] }], ngImport: i0, template: ` <ng-content /> `, isInline: true });
185
- }
186
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.4", ngImport: i0, type: Card, decorators: [{
187
- type: Component,
188
- args: [{
189
- selector: '[ksd-card]',
190
- template: ` <ng-content /> `,
191
- hostDirectives: [
192
- {
193
- directive: CommonInputs,
194
- inputs: ['data-size', 'data-color'],
195
- },
196
- ],
197
- host: {
198
- class: 'ds-card',
199
- '[attr.data-variant]': 'variant()',
200
- '(click)': 'handleClick($event)',
201
- },
202
- }]
203
- }] });
204
-
205
- class CardBlock {
206
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.4", ngImport: i0, type: CardBlock, deps: [], target: i0.ɵɵFactoryTarget.Component });
207
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.0.4", type: CardBlock, isStandalone: true, selector: "[ksd-card-block]", host: { classAttribute: "ds-card__block" }, ngImport: i0, template: `<ng-content />`, isInline: true });
208
- }
209
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.4", ngImport: i0, type: CardBlock, decorators: [{
210
- type: Component,
211
- args: [{
212
- selector: '[ksd-card-block]',
213
- host: {
214
- class: 'ds-card__block',
215
- },
216
- template: `<ng-content />`,
217
- }]
218
- }] });
219
-
220
- class Details {
221
- dataSize = input(undefined, {
222
- // eslint-disable-next-line @angular-eslint/no-input-rename
223
- alias: 'data-size',
224
- });
225
- dataColor = input(undefined, {
226
- // eslint-disable-next-line @angular-eslint/no-input-rename
227
- alias: 'data-color',
228
- });
229
- variant = input('default');
230
- defaultOpen = input(false);
231
- open = input(undefined);
232
- toggled = output();
233
- detailsRef = viewChild('detailsRef');
234
- onToggle(event) {
235
- const details = this.detailsRef()?.nativeElement;
236
- if (details && details.open !== this.open()) {
237
- this.toggled.emit(event);
238
- }
239
- }
240
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.4", ngImport: i0, type: Details, deps: [], target: i0.ɵɵFactoryTarget.Component });
241
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "20.0.4", type: Details, isStandalone: true, selector: "ksd-details", inputs: { dataSize: { classPropertyName: "dataSize", publicName: "data-size", isSignal: true, isRequired: false, transformFunction: null }, dataColor: { classPropertyName: "dataColor", publicName: "data-color", isSignal: true, isRequired: false, transformFunction: null }, variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, defaultOpen: { classPropertyName: "defaultOpen", publicName: "defaultOpen", isSignal: true, isRequired: false, transformFunction: null }, open: { classPropertyName: "open", publicName: "open", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { toggled: "toggled" }, viewQueries: [{ propertyName: "detailsRef", first: true, predicate: ["detailsRef"], descendants: true, isSignal: true }], ngImport: i0, template: `
242
- <u-details
243
- #detailsRef
244
- class="ds-details"
245
- [attr.data-variant]="variant()"
246
- [attr.open]="(open() ?? defaultOpen()) || undefined"
247
- [attr.data-color]="dataColor()"
248
- [attr.data-size]="dataSize()"
249
- (toggle)="onToggle($event)"
250
- >
251
- <u-summary>
252
- <ng-content select="ksd-details-summary" />
253
- </u-summary>
254
- <div>
255
- <ng-content select="ksd-details-content" />
256
- </div>
257
- </u-details>
258
- `, isInline: true, styles: [".ds-card>:host(:last-of-type)>.ds-details{border-bottom:0}.ds-card>:host(:first-of-type)>.ds-details{border-top:0}:host(:not(:first-of-type))>.ds-details{border-top:0;margin-top:0}\n"] });
259
- }
260
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.4", ngImport: i0, type: Details, decorators: [{
261
- type: Component,
262
- args: [{ selector: 'ksd-details', schemas: [CUSTOM_ELEMENTS_SCHEMA], template: `
263
- <u-details
264
- #detailsRef
265
- class="ds-details"
266
- [attr.data-variant]="variant()"
267
- [attr.open]="(open() ?? defaultOpen()) || undefined"
268
- [attr.data-color]="dataColor()"
269
- [attr.data-size]="dataSize()"
270
- (toggle)="onToggle($event)"
271
- >
272
- <u-summary>
273
- <ng-content select="ksd-details-summary" />
274
- </u-summary>
275
- <div>
276
- <ng-content select="ksd-details-content" />
277
- </div>
278
- </u-details>
279
- `, styles: [".ds-card>:host(:last-of-type)>.ds-details{border-bottom:0}.ds-card>:host(:first-of-type)>.ds-details{border-top:0}:host(:not(:first-of-type))>.ds-details{border-top:0;margin-top:0}\n"] }]
280
- }] });
281
-
282
- class DetailsContent {
283
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.4", ngImport: i0, type: DetailsContent, deps: [], target: i0.ɵɵFactoryTarget.Component });
284
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.0.4", type: DetailsContent, isStandalone: true, selector: "ksd-details-content", ngImport: i0, template: `<ng-content />`, isInline: true });
285
- }
286
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.4", ngImport: i0, type: DetailsContent, decorators: [{
287
- type: Component,
288
- args: [{
289
- selector: 'ksd-details-content',
290
- template: `<ng-content />`,
291
- }]
292
- }] });
293
-
294
- class DetailsSummary {
295
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.4", ngImport: i0, type: DetailsSummary, deps: [], target: i0.ɵɵFactoryTarget.Component });
296
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.0.4", type: DetailsSummary, isStandalone: true, selector: "ksd-details-summary", ngImport: i0, template: `<ng-content />`, isInline: true });
297
- }
298
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.4", ngImport: i0, type: DetailsSummary, decorators: [{
299
- type: Component,
300
- args: [{
301
- selector: 'ksd-details-summary',
302
- template: `<ng-content />`,
303
- }]
304
- }] });
305
-
306
- class FieldState {
307
- /**
308
- * Whether the field counter has exceeded its limit
309
- */
310
- hasExceededCounter = signal(false);
311
- /**
312
- * Whether the field has errors projected from the outside
313
- */
314
- hasProjectedErrors = signal(false);
315
- /**
316
- * Whether the field has any errors associated with it
317
- */
318
- hasError = computed(() => this.hasExceededCounter() || this.hasProjectedErrors());
319
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.4", ngImport: i0, type: FieldState, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
320
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.0.4", ngImport: i0, type: FieldState });
321
- }
322
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.4", ngImport: i0, type: FieldState, decorators: [{
323
- type: Injectable
324
- }] });
325
-
326
- class Input {
327
- /**
328
- * The value of the input
329
- */
330
- value = signal('');
331
- /**
332
- * Whether the input is readonly
333
- */
334
- readonly = input(false, { transform: booleanAttribute });
335
- /**
336
- * Disables element
337
- */
338
- disabled = input(false, { transform: booleanAttribute });
339
- /**
340
- * Whether the element is invalid.
341
- */
342
- ariaInvalid = input(false, {
343
- transform: booleanAttribute,
344
- alias: 'aria-invalid',
345
- });
346
- /**
347
- * Displays a character counter. pass a number to set a limit.
348
- */
349
- counter = input(0, { transform: numberAttribute });
350
- fieldState = inject(FieldState, { optional: true });
351
- onClick(event) {
352
- if (this.readonly()) {
353
- event.preventDefault();
354
- }
355
- }
356
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.4", ngImport: i0, type: Input, deps: [], target: i0.ɵɵFactoryTarget.Directive });
357
- static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "20.0.4", type: Input, isStandalone: true, selector: "input[ksd-input], textarea[ksd-input], select[ksd-input]", inputs: { readonly: { classPropertyName: "readonly", publicName: "readonly", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, ariaInvalid: { classPropertyName: "ariaInvalid", publicName: "aria-invalid", isSignal: true, isRequired: false, transformFunction: null }, counter: { classPropertyName: "counter", publicName: "counter", isSignal: true, isRequired: false, transformFunction: null } }, host: { listeners: { "click": "onClick($event)", "input": "value.set($event.target.value)" }, properties: { "attr.readonly": "readonly() ? true : null", "attr.disabled": "disabled() ? true : null", "attr.aria-invalid": "ariaInvalid() ? true : (fieldState?.hasError() ? true: null)" }, classAttribute: "ds-input" }, hostDirectives: [{ directive: CommonInputs, inputs: ["data-size", "data-size", "data-color", "data-color"] }], ngImport: i0 });
358
- }
359
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.4", ngImport: i0, type: Input, decorators: [{
360
- type: Directive,
361
- args: [{
362
- // eslint-disable-next-line @angular-eslint/directive-selector
363
- selector: 'input[ksd-input], textarea[ksd-input], select[ksd-input]',
364
- hostDirectives: [
365
- {
366
- directive: CommonInputs,
367
- inputs: ['data-size', 'data-color'],
368
- },
369
- ],
370
- host: {
371
- class: 'ds-input',
372
- '[attr.readonly]': 'readonly() ? true : null',
373
- '[attr.disabled]': 'disabled() ? true : null',
374
- '[attr.aria-invalid]': 'ariaInvalid() ? true : (fieldState?.hasError() ? true: null)',
375
- '(click)': 'onClick($event)',
376
- '(input)': 'value.set($event.target.value)',
377
- },
378
- }]
379
- }] });
380
-
381
- class Label {
382
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.4", ngImport: i0, type: Label, deps: [], target: i0.ɵɵFactoryTarget.Component });
383
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.0.4", type: Label, isStandalone: true, selector: "ksd-label", hostDirectives: [{ directive: CommonInputs, inputs: ["data-size", "data-size", "data-color", "data-color"] }], ngImport: i0, template: `
384
- <!-- eslint-disable @angular-eslint/template/label-has-associated-control -- Fieldobserver handles binding the label to the input -->
385
- <label class="ds-label"><ng-content /></label>
386
- `, isInline: true });
387
- }
388
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.4", ngImport: i0, type: Label, decorators: [{
389
- type: Component,
390
- args: [{
391
- selector: 'ksd-label',
392
- hostDirectives: [
393
- {
394
- directive: CommonInputs,
395
- inputs: ['data-size', 'data-color'],
396
- },
397
- ],
398
- template: `
399
- <!-- eslint-disable @angular-eslint/template/label-has-associated-control -- Fieldobserver handles binding the label to the input -->
400
- <label class="ds-label"><ng-content /></label>
401
- `,
402
- }]
403
- }] });
404
-
405
- class ValidationMessage {
406
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.4", ngImport: i0, type: ValidationMessage, deps: [], target: i0.ɵɵFactoryTarget.Directive });
407
- static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "20.0.4", type: ValidationMessage, isStandalone: true, selector: "[ksd-validation-message]", host: { attributes: { "data-field": "validation" }, classAttribute: "ds-validation-message" }, ngImport: i0 });
408
- }
409
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.4", ngImport: i0, type: ValidationMessage, decorators: [{
410
- type: Directive,
411
- args: [{
412
- // eslint-disable-next-line @angular-eslint/directive-selector
413
- selector: '[ksd-validation-message]',
414
- host: {
415
- class: 'ds-validation-message',
416
- 'data-field': 'validation',
417
- },
418
- }]
419
- }] });
420
-
421
- class FieldCounter {
422
- /**
423
- * The maximum allowed characters.
424
- *
425
- **/
426
- limit = input.required();
427
- /**
428
- * How many characters have been typed.
429
- *
430
- **/
431
- count = input.required();
432
- remainder = computed(() => this.limit() - this.count());
433
- excessCount = computed(() => Math.abs(this.remainder()));
434
- hasExceededLimit = computed(() => this.count() > this.limit());
435
- fieldState = inject(FieldState);
436
- constructor() {
437
- effect(() => {
438
- this.fieldState.hasExceededCounter.set(this.hasExceededLimit());
439
- });
440
- }
441
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.4", ngImport: i0, type: FieldCounter, deps: [], target: i0.ɵɵFactoryTarget.Component });
442
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.0.4", type: FieldCounter, isStandalone: true, selector: "ksd-field-counter", inputs: { limit: { classPropertyName: "limit", publicName: "limit", isSignal: true, isRequired: true, transformFunction: null }, count: { classPropertyName: "count", publicName: "count", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: `
443
- <div data-field="description" class="ds-sr-only" aria-live="polite">
444
- @if (hasExceededLimit()) {
445
- {{ excessCount() }} tegn for mye
446
- }
447
- </div>
448
- @if (hasExceededLimit()) {
449
- <p ksd-validation-message>{{ excessCount() }} tegn for mye</p>
450
- } @else {
451
- <p data-field="validation">{{ remainder() }} tegn igjen</p>
452
- }
453
- `, isInline: true, styles: [":host>*{margin-top:var(--dsc-field-content-spacing)}\n"], dependencies: [{ kind: "directive", type: ValidationMessage, selector: "[ksd-validation-message]" }] });
454
- }
455
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.4", ngImport: i0, type: FieldCounter, decorators: [{
456
- type: Component,
457
- args: [{ selector: 'ksd-field-counter', imports: [ValidationMessage], template: `
458
- <div data-field="description" class="ds-sr-only" aria-live="polite">
459
- @if (hasExceededLimit()) {
460
- {{ excessCount() }} tegn for mye
461
- }
462
- </div>
463
- @if (hasExceededLimit()) {
464
- <p ksd-validation-message>{{ excessCount() }} tegn for mye</p>
465
- } @else {
466
- <p data-field="validation">{{ remainder() }} tegn igjen</p>
467
- }
468
- `, styles: [":host>*{margin-top:var(--dsc-field-content-spacing)}\n"] }]
469
- }], ctorParameters: () => [] });
470
-
471
- /**
472
- * Lifted from Designsystemet core repo.
473
- * Takes care of binding ids, labels and aria-describedby attributes
474
- *
475
- * @param fieldElement - The field element to observe
476
- * @returns A function to disconnect the observer
477
- * */
478
- function fieldObserver(fieldElement) {
479
- if (!fieldElement)
480
- return;
481
- const elements = new Map();
482
- const typeCounter = new Map(); // Track count for each data-field type
483
- const uuid = `:${Date.now().toString(36)}${Math.random().toString(36).slice(2, 5)}`;
484
- let input = null;
485
- let describedby = '';
486
- const process = (mutations) => {
487
- const changed = [];
488
- const removed = [];
489
- // Merge MutationRecords
490
- for (const mutation of mutations) {
491
- if (mutation.attributeName)
492
- changed.push(mutation.target ?? fieldElement);
493
- // @ts-expect-error - addedNodes is not typed
494
- changed.push(...(mutation.addedNodes || []));
495
- removed.push(...(mutation.removedNodes || []));
496
- }
497
- // Register elements
498
- for (const el of changed) {
499
- if (!isElement(el))
500
- continue;
501
- if (isLabel(el))
502
- elements.set(el, el.htmlFor);
503
- else if (el.hasAttribute('data-field'))
504
- elements.set(el, el.id);
505
- else if (isInputLike(el)) {
506
- input = el;
507
- describedby = el.getAttribute('aria-describedby') || '';
508
- }
509
- }
510
- // Reset removed elements
511
- for (const el of removed) {
512
- if (!isElement(el))
513
- continue;
514
- if (input === el)
515
- input = null;
516
- if (elements.has(el)) {
517
- setAttr(el, isLabel(el) ? 'for' : 'id', elements.get(el));
518
- elements.delete(el);
519
- }
520
- }
521
- // Connect elements
522
- const describedbyIds = [describedby]; // Keep original aria-describedby
523
- const inputId = input?.id || uuid;
524
- // Reset type counters since we reprocess all elements
525
- typeCounter.clear();
526
- for (const [el, value] of elements) {
527
- const descriptionType = el.getAttribute('data-field');
528
- let id;
529
- if (descriptionType) {
530
- // Increment type counter for this type
531
- const count = (typeCounter.get(descriptionType) || 0) + 1;
532
- typeCounter.set(descriptionType, count);
533
- id = `${inputId}:${descriptionType}:${count}`;
534
- }
535
- else {
536
- id = inputId;
537
- }
538
- if (!value)
539
- setAttr(el, isLabel(el) ? 'for' : 'id', id); // Ensure we have a value
540
- if (descriptionType === 'validation')
541
- describedbyIds.unshift(el.id); // Validations to the front
542
- else if (descriptionType)
543
- describedbyIds.push(el.id); // Other descriptions to the back
544
- }
545
- setAttr(input, 'id', inputId);
546
- setAttr(input, 'aria-describedby', describedbyIds.join(' ').trim());
547
- };
548
- const observer = createOptimizedMutationObserver(process);
549
- observer.observe(fieldElement, {
550
- attributeFilter: ['id', 'for', 'aria-describedby'],
551
- attributes: true,
552
- childList: true,
553
- subtree: true,
554
- });
555
- process([{ addedNodes: fieldElement.querySelectorAll('*') }]); // Initial setup
556
- observer.takeRecords(); // Clear initial setup queue
557
- return () => observer.disconnect();
558
- }
559
- // Utilities
560
- const isElement = (node) => node instanceof Element;
561
- const isLabel = (node) => node instanceof HTMLLabelElement;
562
- const isInputLike = (node) => node instanceof HTMLElement &&
563
- 'validity' in node &&
564
- !(node instanceof HTMLButtonElement); // Matches input, textarea, select and form accosiated custom elements
565
- const setAttr = (el, name, value) => value ? el?.setAttribute(name, value) : el?.removeAttribute(name);
566
- // Speed up MutationObserver by debouncing, clearing internal queue after changes and only running when page is visible
567
- function createOptimizedMutationObserver(callback) {
568
- const queue = [];
569
- const observer = new MutationObserver((mutations) => {
570
- if (!queue.length)
571
- requestAnimationFrame(process);
572
- queue.push(...mutations);
573
- });
574
- const process = () => {
575
- callback(queue, observer);
576
- queue.length = 0; // Reset queue
577
- observer.takeRecords(); // Clear queue due to DOM changes in callback
578
- };
579
- return observer;
580
- }
581
-
582
- /**
583
- * Use the Field component to connect inputs and labels
584
- */
585
- class Field {
586
- /**
587
- * Position of toggle inputs (radio, checkbox, switch) in field
588
- * @default start
589
- */
590
- position = input('start');
591
- fieldState = inject(FieldState);
592
- input = contentChild(Input);
593
- label = contentChild(Label);
594
- projectedErrors = contentChildren(ValidationMessage);
595
- el = inject(ElementRef);
596
- count = computed(() => this.input()?.value().length);
597
- limit = computed(() => this.input()?.counter());
598
- hasCounter = computed(() => this.limit());
599
- constructor() {
600
- afterNextRender(() => {
601
- fieldObserver(this.el.nativeElement);
602
- });
603
- effect(() => {
604
- this.fieldState.hasProjectedErrors.set(this.projectedErrors().length > 0);
605
- });
606
- }
607
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.4", ngImport: i0, type: Field, deps: [], target: i0.ɵɵFactoryTarget.Component });
608
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.0.4", type: Field, isStandalone: true, selector: "ksd-field", inputs: { position: { classPropertyName: "position", publicName: "position", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "attr.dataPosition": "position()" }, classAttribute: "ds-field" }, providers: [FieldState], queries: [{ propertyName: "input", first: true, predicate: Input, descendants: true, isSignal: true }, { propertyName: "label", first: true, predicate: Label, descendants: true, isSignal: true }, { propertyName: "projectedErrors", predicate: ValidationMessage, isSignal: true }], hostDirectives: [{ directive: CommonInputs, inputs: ["data-size", "data-size", "data-color", "data-color"] }], ngImport: i0, template: `
609
- <ng-content />
610
- @if (hasCounter()) {
611
- <ksd-field-counter [limit]="limit() ?? 0" [count]="count() ?? 0" />
612
- }
613
- `, isInline: true, dependencies: [{ kind: "component", type: FieldCounter, selector: "ksd-field-counter", inputs: ["limit", "count"] }] });
614
- }
615
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.4", ngImport: i0, type: Field, decorators: [{
616
- type: Component,
617
- args: [{
618
- selector: 'ksd-field',
619
- hostDirectives: [
620
- {
621
- directive: CommonInputs,
622
- inputs: ['data-size', 'data-color'],
623
- },
624
- ],
625
- host: {
626
- class: 'ds-field',
627
- '[attr.dataPosition]': 'position()',
628
- },
629
- template: `
630
- <ng-content />
631
- @if (hasCounter()) {
632
- <ksd-field-counter [limit]="limit() ?? 0" [count]="count() ?? 0" />
633
- }
634
- `,
635
- imports: [FieldCounter],
636
- providers: [FieldState],
637
- }]
638
- }], ctorParameters: () => [] });
639
-
640
- class FieldDescription {
641
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.4", ngImport: i0, type: FieldDescription, deps: [], target: i0.ɵɵFactoryTarget.Component });
642
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.0.4", type: FieldDescription, isStandalone: true, selector: "[ksd-field-description]", host: { attributes: { "data-field": "description" } }, ngImport: i0, template: `<ng-content />`, isInline: true });
643
- }
644
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.4", ngImport: i0, type: FieldDescription, decorators: [{
645
- type: Component,
646
- args: [{
647
- selector: '[ksd-field-description]',
648
- host: {
649
- 'data-field': 'description',
650
- },
651
- template: `<ng-content />`,
652
- }]
653
- }] });
654
-
655
- class FieldError {
656
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.4", ngImport: i0, type: FieldError, deps: [], target: i0.ɵɵFactoryTarget.Component });
657
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.0.4", type: FieldError, isStandalone: true, selector: "[ksd-error]", hostDirectives: [{ directive: ValidationMessage }], ngImport: i0, template: `<ng-content />`, isInline: true });
658
- }
659
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.4", ngImport: i0, type: FieldError, decorators: [{
660
- type: Component,
661
- args: [{
662
- selector: '[ksd-error]',
663
- template: `<ng-content />`,
664
- hostDirectives: [
665
- {
666
- directive: ValidationMessage,
667
- },
668
- ],
669
- }]
670
- }] });
671
-
672
- class Fieldset {
673
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.4", ngImport: i0, type: Fieldset, deps: [], target: i0.ɵɵFactoryTarget.Component });
674
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.0.4", type: Fieldset, isStandalone: true, selector: "fieldset[ksd-fieldset]", host: { attributes: { "role": "fieldset" }, classAttribute: "ds-fieldset" }, ngImport: i0, template: ` <ng-content /> `, isInline: true });
675
- }
676
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.4", ngImport: i0, type: Fieldset, decorators: [{
677
- type: Component,
678
- args: [{
679
- selector: 'fieldset[ksd-fieldset]',
680
- host: {
681
- role: 'fieldset',
682
- class: 'ds-fieldset',
683
- },
684
- template: ` <ng-content /> `,
685
- }]
686
- }] });
687
-
688
- class FieldsetDescription {
689
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.4", ngImport: i0, type: FieldsetDescription, deps: [], target: i0.ɵɵFactoryTarget.Component });
690
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.0.4", type: FieldsetDescription, isStandalone: true, selector: "p[ksd-fieldset-description]", ngImport: i0, template: `<ng-content />`, isInline: true });
691
- }
692
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.4", ngImport: i0, type: FieldsetDescription, decorators: [{
693
- type: Component,
694
- args: [{
695
- selector: 'p[ksd-fieldset-description]',
696
- template: `<ng-content />`,
697
- host: {},
698
- }]
699
- }] });
700
-
701
- class FieldsetLegend {
702
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.4", ngImport: i0, type: FieldsetLegend, deps: [], target: i0.ɵɵFactoryTarget.Component });
703
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.0.4", type: FieldsetLegend, isStandalone: true, selector: "legend[ksd-fieldset-legend]", host: { attributes: { "role": "legend" }, classAttribute: "ds-label" }, ngImport: i0, template: ` <ng-content /> `, isInline: true });
704
- }
705
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.4", ngImport: i0, type: FieldsetLegend, decorators: [{
706
- type: Component,
707
- args: [{
708
- selector: 'legend[ksd-fieldset-legend]',
709
- host: {
710
- role: 'legend',
711
- class: 'ds-label',
712
- },
713
- template: ` <ng-content /> `,
714
- }]
715
- }] });
716
-
717
- class Paragraph {
718
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.4", ngImport: i0, type: Paragraph, deps: [], target: i0.ɵɵFactoryTarget.Component });
719
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.0.4", type: Paragraph, isStandalone: true, selector: "p[ksd-paragraph]", host: { classAttribute: "ds-paragraph" }, ngImport: i0, template: `<ng-content />`, isInline: true });
720
- }
721
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.4", ngImport: i0, type: Paragraph, decorators: [{
722
- type: Component,
723
- args: [{
724
- selector: 'p[ksd-paragraph]',
725
- template: `<ng-content />`,
726
- host: {
727
- class: 'ds-paragraph',
728
- },
729
- }]
730
- }] });
731
-
732
- /* eslint-disable @angular-eslint/no-input-rename */
733
- class Popover {
734
- // use popoverId instead of id since id will be put on the angular element and not the popover-div
735
- popoverId = input.required();
736
- placement = input('top');
737
- autoPlacement = input(true, { transform: booleanAttribute });
738
- // for controlled component
739
- open = input(undefined, { transform: booleanAttribute });
740
- /*
741
- the naming here is different from Designsystemet
742
- since we need to use outputs for onOpen and onClose
743
- */
744
- triggeredClose = output();
745
- triggeredOpen = output();
746
- internalOpen = signal(false);
747
- controlledOpen = computed(() => this.open() ?? this.internalOpen());
748
- variant = input('default');
749
- dataSize = input('md', { alias: 'data-size' });
750
- dataColor = input('neutral', {
751
- alias: 'data-color',
752
- });
753
- popoverRef = viewChild('myPopover');
754
- // enable controlled component
755
- controlledComponent = effect((onCleanup) => {
756
- const popover = this.popoverRef()?.nativeElement;
757
- const handleClick = (event) => {
758
- const el = event.target;
759
- const isTrigger = el?.closest?.(`[popovertarget="${this.popoverId()}"]`);
760
- const isOutside = !isTrigger && !popover?.contains(el);
761
- if (isTrigger) {
762
- event.preventDefault(); // Prevent native Popover API
763
- this.internalOpen.update((open) => !open);
764
- this.triggeredOpen.emit();
765
- }
766
- if (isOutside) {
767
- this.internalOpen.set(false);
768
- this.triggeredClose.emit();
769
- }
770
- };
771
- const handleKeydown = (event) => {
772
- if (event.key !== 'Escape' || !this.controlledOpen())
773
- return;
774
- event.preventDefault(); // Prevent closing fullscreen in Safari
775
- this.internalOpen.set(false);
776
- this.triggeredClose.emit();
777
- };
778
- popover?.togglePopover?.(this.controlledOpen());
779
- document.addEventListener('click', handleClick, true); // Use capture to execute before React event API
780
- document.addEventListener('keydown', handleKeydown);
781
- onCleanup(() => {
782
- document.removeEventListener('click', handleClick, true);
783
- document.removeEventListener('keydown', handleKeydown);
784
- });
785
- }, {});
786
- positionPopover = effect(() => {
787
- const popover = this.popoverRef()?.nativeElement;
788
- const trigger = document.querySelector(`[popovertarget="${this.popoverId()}"]`);
789
- const placement = this.placement();
790
- if (popover && trigger && this.controlledOpen()) {
791
- autoUpdate(trigger, popover, () => {
792
- computePosition(trigger, popover, {
793
- placement,
794
- strategy: 'fixed',
795
- middleware: [
796
- offset((data) => {
797
- // get pseudo element arrow size
798
- const styles = getComputedStyle(data.elements.floating, '::before');
799
- return parseFloat(styles.height);
800
- }),
801
- ...(this.autoPlacement()
802
- ? [flip({ fallbackAxisSideDirection: 'start' }), shift()]
803
- : []),
804
- this.arrowPseudoElement,
805
- ],
806
- }).then(({ x, y }) => {
807
- popover.style.translate = `${x}px ${y}px`;
808
- });
809
- });
810
- }
811
- }, {});
812
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
813
- arrowPseudoElement = {
814
- name: 'ArrowPseudoElement',
815
- fn(data) {
816
- const { elements, rects, placement } = data;
817
- let arrowX = `${Math.round(rects.reference.width / 2 + rects.reference.x - data.x)}px`;
818
- let arrowY = `${Math.round(rects.reference.height / 2 + rects.reference.y - data.y)}px`;
819
- if (rects.reference.width > rects.floating.width) {
820
- arrowX = `${Math.round(rects.floating.width / 2)}px`;
821
- }
822
- if (rects.reference.height > rects.floating.height) {
823
- arrowY = `${Math.round(rects.floating.height / 2)}px`;
824
- }
825
- switch (placement.split('-')[0]) {
826
- case 'top':
827
- arrowY = '100%';
828
- break;
829
- case 'right':
830
- arrowX = '0';
831
- break;
832
- case 'bottom':
833
- arrowY = '0';
834
- break;
835
- case 'left':
836
- arrowX = '100%';
837
- break;
838
- }
839
- elements.floating.setAttribute('data-placement', placement.split('-')[0]); // We only need top/left/right/bottom
840
- elements.floating.style.setProperty('--ds-popover-arrow-x', arrowX);
841
- elements.floating.style.setProperty('--ds-popover-arrow-y', arrowY);
842
- return data;
843
- },
844
- };
845
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.4", ngImport: i0, type: Popover, deps: [], target: i0.ɵɵFactoryTarget.Component });
846
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.0.4", type: Popover, isStandalone: true, selector: "ksd-popover", inputs: { popoverId: { classPropertyName: "popoverId", publicName: "popoverId", isSignal: true, isRequired: true, transformFunction: null }, placement: { classPropertyName: "placement", publicName: "placement", isSignal: true, isRequired: false, transformFunction: null }, autoPlacement: { classPropertyName: "autoPlacement", publicName: "autoPlacement", isSignal: true, isRequired: false, transformFunction: null }, open: { classPropertyName: "open", publicName: "open", isSignal: true, isRequired: false, transformFunction: null }, variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, dataSize: { classPropertyName: "dataSize", publicName: "data-size", isSignal: true, isRequired: false, transformFunction: null }, dataColor: { classPropertyName: "dataColor", publicName: "data-color", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { triggeredClose: "triggeredClose", triggeredOpen: "triggeredOpen" }, viewQueries: [{ propertyName: "popoverRef", first: true, predicate: ["myPopover"], descendants: true, isSignal: true }], ngImport: i0, template: `
847
- <div
848
- #myPopover
849
- popover="manual"
850
- class="ds-popover"
851
- data-testid="popover"
852
- [id]="popoverId()"
853
- [attr.data-size]="dataSize()"
854
- [attr.data-color]="dataColor()"
855
- [attr.data-variant]="variant()"
856
- >
857
- @if (controlledOpen()) {
858
- <ng-content />
859
- }
860
- </div>
861
- `, isInline: true });
862
- }
863
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.4", ngImport: i0, type: Popover, decorators: [{
864
- type: Component,
865
- args: [{
866
- selector: 'ksd-popover',
867
- template: `
868
- <div
869
- #myPopover
870
- popover="manual"
871
- class="ds-popover"
872
- data-testid="popover"
873
- [id]="popoverId()"
874
- [attr.data-size]="dataSize()"
875
- [attr.data-color]="dataColor()"
876
- [attr.data-variant]="variant()"
877
- >
878
- @if (controlledOpen()) {
879
- <ng-content />
880
- }
881
- </div>
882
- `,
883
- imports: [],
884
- }]
885
- }] });
886
-
887
- const logIfDevMode = ({ component, message, }) => {
888
- if (isDevMode()) {
889
- console.log(`[${component}] ${message}`);
890
- }
891
- };
892
-
893
- /**
894
- * Search input
895
- *
896
- * Used within Search to provide a search input.
897
- */
898
- class SearchInput {
899
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.4", ngImport: i0, type: SearchInput, deps: [], target: i0.ɵɵFactoryTarget.Directive });
900
- static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "20.0.4", type: SearchInput, isStandalone: true, selector: "input[ksd-search-input]", host: { attributes: { "type": "search", "placeholder": "" }, classAttribute: "ds-input" }, ngImport: i0 });
901
- }
902
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.4", ngImport: i0, type: SearchInput, decorators: [{
903
- type: Directive,
904
- args: [{
905
- // eslint-disable-next-line @angular-eslint/directive-selector
906
- selector: 'input[ksd-search-input]',
907
- standalone: true,
908
- host: {
909
- class: 'ds-input',
910
- type: 'search',
911
- placeholder: '', // Need empty placeholder to enable show/hide for clear button
912
- },
913
- }]
914
- }] });
915
-
916
- /**
917
- * Search Component
918
- *
919
- * Use to contain the search input and buttons.
920
- * Only `SearchInput` is required, while `SearchClear` and `SearchButton` are optional.
921
- *
922
- * @example
923
- * <div ksd-search>
924
- * <input ksd-search-input />
925
- * <button ksd-search-clear></button>
926
- * <button ksd-search-button></button>
927
- * </div>
928
- */
929
- class Search {
930
- input = contentChild(SearchInput);
931
- constructor() {
932
- afterNextRender(() => {
933
- if (!this.input()) {
934
- logIfDevMode({
935
- component: 'Search',
936
- message: 'Missing required elements: ksd-search-input must be provided as child. Check imports and markup.',
937
- });
938
- }
939
- });
940
- }
941
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.4", ngImport: i0, type: Search, deps: [], target: i0.ɵɵFactoryTarget.Component });
942
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "20.0.4", type: Search, isStandalone: true, selector: "ksd-search", host: { classAttribute: "ds-search" }, queries: [{ propertyName: "input", first: true, predicate: SearchInput, descendants: true, isSignal: true }], hostDirectives: [{ directive: CommonInputs, inputs: ["data-size", "data-size", "data-color", "data-color"] }], ngImport: i0, template: `
943
- <ng-content select="[ksd-search-input]" />
944
- <ng-content select="[ksd-search-clear]" />
945
- <ng-content select="[ksd-search-button]" />
946
- `, isInline: true });
947
- }
948
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.4", ngImport: i0, type: Search, decorators: [{
949
- type: Component,
950
- args: [{
951
- selector: 'ksd-search',
952
- template: `
953
- <ng-content select="[ksd-search-input]" />
954
- <ng-content select="[ksd-search-clear]" />
955
- <ng-content select="[ksd-search-button]" />
956
- `,
957
- host: {
958
- class: 'ds-search',
959
- },
960
- hostDirectives: [
961
- {
962
- directive: CommonInputs,
963
- inputs: ['data-size', 'data-color'],
964
- },
965
- ],
966
- }]
967
- }], ctorParameters: () => [] });
968
-
969
- /**
970
- * Search button
971
- *
972
- * Used within Search to provide a submit button.
973
- *
974
- * @param {('primary' | 'secondary')} [variant] - Specify which button variant to use
975
- * @param {string} [aria-label] - Aria label for the button
976
- *
977
- */
978
- class SearchButton {
979
- /**
980
- * Specify which button variant to use
981
- *
982
- * @default 'primary'
983
- */
984
- variant = input('primary');
985
- /**
986
- * Aria label for the button
987
- */
988
- ariaLabel = input('', { alias: 'aria-label' });
989
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.4", ngImport: i0, type: SearchButton, deps: [], target: i0.ɵɵFactoryTarget.Directive });
990
- static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "20.0.4", type: SearchButton, isStandalone: true, selector: "button[ksd-search-button]", inputs: { variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "aria-label", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "type": "submit" }, properties: { "attr.aria-label": "this.ariaLabel()", "attr.data-variant": "this.variant()" }, classAttribute: "ds-button" }, ngImport: i0 });
991
- }
992
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.4", ngImport: i0, type: SearchButton, decorators: [{
993
- type: Directive,
994
- args: [{
995
- // eslint-disable-next-line @angular-eslint/directive-selector
996
- selector: 'button[ksd-search-button]',
997
- standalone: true,
998
- host: {
999
- class: 'ds-button',
1000
- type: 'submit',
1001
- '[attr.aria-label]': 'this.ariaLabel()',
1002
- '[attr.data-variant]': 'this.variant()',
1003
- },
1004
- }]
1005
- }] });
1006
-
1007
- /**
1008
- * Search clear button
1009
- *
1010
- * Used within Search to provide a clear button.
1011
- *
1012
- * @param {string} [aria-label] - Aria label for the button.
1013
- *
1014
- * @event clearInput - Emitted when the clear button is clicked.
1015
- * Use this to notify controlled forms that the input should be cleared.
1016
- *
1017
- */
1018
- class SearchClear {
1019
- /**
1020
- * Aria label for the button
1021
- * @default 'Tøm'
1022
- */
1023
- ariaLabel = input('Tøm', { alias: 'aria-label' });
1024
- /**
1025
- * Output to notify controlled forms that input should be cleared
1026
- */
1027
- clearInput = output();
1028
- handleClear(e) {
1029
- const target = e.target;
1030
- let inputElement = null;
1031
- if (target instanceof HTMLElement) {
1032
- inputElement = target.closest('.ds-search')?.querySelector('input');
1033
- }
1034
- if (!inputElement)
1035
- throw new Error('Input is missing');
1036
- if (!(inputElement instanceof HTMLInputElement)) {
1037
- throw new Error('Input is not an input element');
1038
- }
1039
- e.preventDefault();
1040
- inputElement.value = '';
1041
- this.clearInput.emit();
1042
- inputElement.focus();
1043
- }
1044
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.4", ngImport: i0, type: SearchClear, deps: [], target: i0.ɵɵFactoryTarget.Directive });
1045
- static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "20.0.4", type: SearchClear, isStandalone: true, selector: "button[ksd-search-clear]", inputs: { ariaLabel: { classPropertyName: "ariaLabel", publicName: "aria-label", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { clearInput: "clearInput" }, host: { attributes: { "type": "reset" }, listeners: { "click": "handleClear($event)" }, properties: { "attr.data-variant": "'tertiary'", "attr.aria-label": "this.ariaLabel()" }, classAttribute: "ds-button" }, ngImport: i0 });
1046
- }
1047
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.4", ngImport: i0, type: SearchClear, decorators: [{
1048
- type: Directive,
1049
- args: [{
1050
- // eslint-disable-next-line @angular-eslint/directive-selector
1051
- selector: 'button[ksd-search-clear]',
1052
- standalone: true,
1053
- host: {
1054
- class: 'ds-button',
1055
- type: 'reset',
1056
- '[attr.data-variant]': "'tertiary'",
1057
- '[attr.aria-label]': 'this.ariaLabel()',
1058
- '(click)': 'handleClear($event)',
1059
- },
1060
- }]
1061
- }] });
1062
-
1063
- /**
1064
- * Generated bundle index. Do not edit.
1065
- */
1066
-
1067
- export { Button, Card, CardBlock, CommonInputs, Details, DetailsContent, DetailsSummary, Field, FieldDescription, FieldError, Fieldset, FieldsetDescription, FieldsetLegend, Input, Label, Paragraph, Popover, Search, SearchButton, SearchClear, SearchInput, Spinner, ValidationMessage };
1068
- //# sourceMappingURL=ks-digital-designsystem-angular.mjs.map