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

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 (140) hide show
  1. package/__internals/index.d.ts +54 -0
  2. package/alert/index.d.ts +17 -0
  3. package/button/index.d.ts +29 -0
  4. package/card/index.d.ts +22 -0
  5. package/details/index.d.ts +26 -0
  6. package/fesm2022/ks-digital-designsystem-angular-__internals.mjs +50 -0
  7. package/fesm2022/ks-digital-designsystem-angular-__internals.mjs.map +1 -0
  8. package/fesm2022/ks-digital-designsystem-angular-alert.mjs +35 -0
  9. package/fesm2022/ks-digital-designsystem-angular-alert.mjs.map +1 -0
  10. package/fesm2022/ks-digital-designsystem-angular-button.mjs +63 -0
  11. package/fesm2022/ks-digital-designsystem-angular-button.mjs.map +1 -0
  12. package/fesm2022/ks-digital-designsystem-angular-card.mjs +70 -0
  13. package/fesm2022/ks-digital-designsystem-angular-card.mjs.map +1 -0
  14. package/fesm2022/ks-digital-designsystem-angular-details.mjs +100 -0
  15. package/fesm2022/ks-digital-designsystem-angular-details.mjs.map +1 -0
  16. package/fesm2022/ks-digital-designsystem-angular-forms.mjs +408 -0
  17. package/fesm2022/ks-digital-designsystem-angular-forms.mjs.map +1 -0
  18. package/fesm2022/ks-digital-designsystem-angular-paragraph.mjs +24 -0
  19. package/fesm2022/ks-digital-designsystem-angular-paragraph.mjs.map +1 -0
  20. package/fesm2022/ks-digital-designsystem-angular-popover.mjs +165 -0
  21. package/fesm2022/ks-digital-designsystem-angular-popover.mjs.map +1 -0
  22. package/fesm2022/ks-digital-designsystem-angular-search.mjs +181 -0
  23. package/fesm2022/ks-digital-designsystem-angular-search.mjs.map +1 -0
  24. package/fesm2022/ks-digital-designsystem-angular-spinner.mjs +89 -0
  25. package/fesm2022/ks-digital-designsystem-angular-spinner.mjs.map +1 -0
  26. package/fesm2022/ks-digital-designsystem-angular-validation-message.mjs +25 -0
  27. package/fesm2022/ks-digital-designsystem-angular-validation-message.mjs.map +1 -0
  28. package/fesm2022/ks-digital-designsystem-angular.mjs +7 -0
  29. package/fesm2022/ks-digital-designsystem-angular.mjs.map +1 -0
  30. package/forms/index.d.ts +100 -0
  31. package/index.d.ts +2 -0
  32. package/package.json +59 -20
  33. package/paragraph/index.d.ts +8 -0
  34. package/popover/index.d.ts +25 -0
  35. package/search/index.d.ts +84 -0
  36. package/spinner/index.d.ts +25 -0
  37. package/validation-message/index.d.ts +8 -0
  38. package/.storybook/customTheme.ts +0 -15
  39. package/.storybook/default-args.ts +0 -18
  40. package/.storybook/main.ts +0 -27
  41. package/.storybook/manager.ts +0 -10
  42. package/.storybook/preview-head.html +0 -16
  43. package/.storybook/preview.ts +0 -70
  44. package/.storybook/themes.ts +0 -9
  45. package/.storybook/tsconfig.json +0 -16
  46. package/.storybook/vite.config.mts +0 -5
  47. package/eslint.config.mjs +0 -28
  48. package/ng-package.json +0 -9
  49. package/project.json +0 -81
  50. package/src/components/alert/alert.mdx +0 -46
  51. package/src/components/alert/alert.spec.ts +0 -33
  52. package/src/components/alert/alert.stories.ts +0 -138
  53. package/src/components/alert/alert.ts +0 -46
  54. package/src/components/alert/index.ts +0 -1
  55. package/src/components/button/button.mdx +0 -40
  56. package/src/components/button/button.spec.ts +0 -86
  57. package/src/components/button/button.stories.ts +0 -123
  58. package/src/components/button/button.ts +0 -60
  59. package/src/components/button/index.ts +0 -1
  60. package/src/components/card/card-block.ts +0 -10
  61. package/src/components/card/card.mdx +0 -100
  62. package/src/components/card/card.spec.ts +0 -70
  63. package/src/components/card/card.stories.ts +0 -101
  64. package/src/components/card/card.ts +0 -44
  65. package/src/components/card/index.ts +0 -2
  66. package/src/components/checkbox/README.md +0 -13
  67. package/src/components/checkbox/checkbox.mdx +0 -50
  68. package/src/components/checkbox/checkbox.spec.ts +0 -21
  69. package/src/components/checkbox/checkbox.stories.ts +0 -182
  70. package/src/components/checkbox/index.ts +0 -0
  71. package/src/components/colors.ts +0 -36
  72. package/src/components/common-inputs.ts +0 -30
  73. package/src/components/details/controlled-details.ts +0 -63
  74. package/src/components/details/details-content.ts +0 -7
  75. package/src/components/details/details-summary.ts +0 -7
  76. package/src/components/details/details.mdx +0 -89
  77. package/src/components/details/details.spec.ts +0 -56
  78. package/src/components/details/details.stories.ts +0 -129
  79. package/src/components/details/details.ts +0 -69
  80. package/src/components/details/index.ts +0 -3
  81. package/src/components/field/field-counter.ts +0 -56
  82. package/src/components/field/field-description.ts +0 -10
  83. package/src/components/field/field-error.ts +0 -13
  84. package/src/components/field/field-observer.ts +0 -121
  85. package/src/components/field/field-state.ts +0 -21
  86. package/src/components/field/field.mdx +0 -40
  87. package/src/components/field/field.spec.ts +0 -131
  88. package/src/components/field/field.stories.ts +0 -98
  89. package/src/components/field/field.ts +0 -70
  90. package/src/components/field/index.ts +0 -3
  91. package/src/components/fieldset/fieldset-description.ts +0 -8
  92. package/src/components/fieldset/fieldset-legend.ts +0 -11
  93. package/src/components/fieldset/fieldset.spec.ts +0 -80
  94. package/src/components/fieldset/fieldset.ts +0 -11
  95. package/src/components/fieldset/index.ts +0 -3
  96. package/src/components/input/index.ts +0 -1
  97. package/src/components/input/input.mdx +0 -11
  98. package/src/components/input/input.spec.ts +0 -25
  99. package/src/components/input/input.stories.ts +0 -72
  100. package/src/components/input/input.ts +0 -67
  101. package/src/components/label/index.ts +0 -1
  102. package/src/components/label/label.ts +0 -17
  103. package/src/components/paragraph/index.ts +0 -1
  104. package/src/components/paragraph/paragraph.ts +0 -10
  105. package/src/components/popover/controlled-popover.ts +0 -62
  106. package/src/components/popover/index.ts +0 -1
  107. package/src/components/popover/popover.mdx +0 -81
  108. package/src/components/popover/popover.spec.ts +0 -143
  109. package/src/components/popover/popover.stories.ts +0 -63
  110. package/src/components/popover/popover.ts +0 -186
  111. package/src/components/radio/radio.mdx +0 -117
  112. package/src/components/radio/radio.stories.ts +0 -226
  113. package/src/components/search/index.ts +0 -4
  114. package/src/components/search/search-button.ts +0 -35
  115. package/src/components/search/search-clear.ts +0 -57
  116. package/src/components/search/search-input.ts +0 -18
  117. package/src/components/search/search.mdx +0 -56
  118. package/src/components/search/search.spec.ts +0 -48
  119. package/src/components/search/search.stories.ts +0 -205
  120. package/src/components/search/search.ts +0 -50
  121. package/src/components/spinner/index.ts +0 -1
  122. package/src/components/spinner/spinner.mdx +0 -24
  123. package/src/components/spinner/spinner.spec.ts +0 -13
  124. package/src/components/spinner/spinner.stories.ts +0 -54
  125. package/src/components/spinner/spinner.ts +0 -62
  126. package/src/components/switch/switch.mdx +0 -82
  127. package/src/components/switch/switch.stories.ts +0 -94
  128. package/src/components/textarea/textarea.mdx +0 -14
  129. package/src/components/textarea/textarea.stories.ts +0 -52
  130. package/src/components/validation-message/index.ts +0 -1
  131. package/src/components/validation-message/validation-message.ts +0 -11
  132. package/src/index.ts +0 -14
  133. package/src/test-setup.ts +0 -12
  134. package/src/utils/log-if-devmode.ts +0 -13
  135. package/src/utils/random-id.ts +0 -3
  136. package/tsconfig.json +0 -34
  137. package/tsconfig.lib.json +0 -28
  138. package/tsconfig.lib.prod.json +0 -9
  139. package/tsconfig.spec.json +0 -30
  140. package/vite.config.mts +0 -35
@@ -1,63 +0,0 @@
1
- import { Component, computed, signal } from '@angular/core'
2
- import { Button } from '../button'
3
- import { Details } from './details'
4
- import { DetailsContent } from './details-content'
5
- import { DetailsSummary } from './details-summary'
6
-
7
- @Component({
8
- selector: 'fiks-controlled-details',
9
- imports: [Details, DetailsContent, DetailsSummary, Button],
10
- template: `
11
- <button
12
- ksd-button
13
- variant="secondary"
14
- style="margin-bottom: .5em;"
15
- (click)="toggleOpen()"
16
- >
17
- {{ toggleOpenText() }}
18
- </button>
19
- <ksd-details [open]="open1()" (toggled)="open1.set(!open1())">
20
- <ksd-details-summary>Enkeltpersonforetak</ksd-details-summary>
21
- <ksd-details-content>
22
- Skal du starte for deg selv? Enkeltpersonforetak er ofte den enkleste
23
- måten å etablere bedrift på. Denne organisasjonsformen har både fordeler
24
- og ulemper. Det gir deg stor grad av frihet, men kan også gi betydelig
25
- risiko fordi du har personlig ansvar for økonomien.
26
- </ksd-details-content>
27
- </ksd-details>
28
- <ksd-details [open]="open2()" (toggled)="open2.set(!open2())">
29
- <ksd-details-summary>Aksjeselskap (AS)</ksd-details-summary>
30
- <ksd-details-content>
31
- Planlegger du å starte næringsvirksomhet alene eller sammen med andre?
32
- Innebærer næringsvirksomheten en økonomisk risiko? Vil du ha rettigheter
33
- som arbeidstaker og muligheten til at andre kan investere i selskapet
34
- ditt? Da kan aksjeselskap være en hensiktsmessig organisasjonsform.
35
- </ksd-details-content>
36
- </ksd-details>
37
- <ksd-details [open]="open3()" (toggled)="open3.set(!open3())">
38
- <ksd-details-summary>Ansvarlig selskap (ANS/DA)</ksd-details-summary>
39
- <ksd-details-content>
40
- Skal dere starte opp egen virksomhet sammen? Samarbeider dere godt?
41
- Krever virksomheten små investeringer og innebærer liten økonomisk
42
- risiko? Da kan ansvarlig selskap være aktuelt.
43
- </ksd-details-content>
44
- </ksd-details>
45
- `,
46
- })
47
- export class ControlledDetails {
48
- open1 = signal(false)
49
- open2 = signal(false)
50
- open3 = signal(false)
51
-
52
- isOpen = computed(() =>
53
- [this.open1(), this.open2(), this.open3()].every(Boolean),
54
- )
55
- toggleOpenText = computed(() => (this.isOpen() ? 'Lukk alle' : 'Åpne alle'))
56
-
57
- toggleOpen = () => {
58
- const isOpen = this.isOpen()
59
- this.open1.set(!isOpen)
60
- this.open2.set(!isOpen)
61
- this.open3.set(!isOpen)
62
- }
63
- }
@@ -1,7 +0,0 @@
1
- import { Component } from '@angular/core'
2
-
3
- @Component({
4
- selector: 'ksd-details-content',
5
- template: `<ng-content />`,
6
- })
7
- export class DetailsContent {}
@@ -1,7 +0,0 @@
1
- import { Component } from '@angular/core'
2
-
3
- @Component({
4
- selector: 'ksd-details-summary',
5
- template: `<ng-content />`,
6
- })
7
- export class DetailsSummary {}
@@ -1,89 +0,0 @@
1
- import { Meta, Canvas, Controls, Primary } from '@storybook/addon-docs/blocks'
2
- import * as DetailsStories from './details.stories'
3
-
4
- <Meta of={DetailsStories} />
5
-
6
- # Details
7
-
8
- Med `Details` kan du presentera mykje innhald på liten plass i ei eller fleire rader. Heile rada er klikkbar og let brukarar opna eller lukka visninga av innhaldet under.
9
-
10
- <Primary />
11
- <Controls />
12
-
13
- ## Bruk
14
-
15
- ```html
16
- /* Utan ramme */
17
- <ksd-details>
18
- <ksd-details-summary>Vedlegg</ksd-details-summary>
19
- <ksd-details-content>Vedlegg 1, vedlegg 2, vedlegg 3</ksd-details-content>
20
- </ksd-details>
21
-
22
- /* Med ramme */
23
-
24
- <article ksd-card>
25
- <ksd-details>
26
- <ksd-details-summary>Vedlegg</ksd-details-summary>
27
- <ksd-details-content>Vedlegg 1, vedlegg 2, vedlegg 3</ksd-details-content>
28
- </ksd-details>
29
- </article>
30
- ```
31
-
32
- ## Kodeeksempler
33
-
34
- ### Med ramme
35
-
36
- `Details` kan visast med ramme ved å leggje den i eit `Card`.
37
- Dette kan passa i tilfelle der `Details` ikkje fyller heile sida, eller når det berre er ei rad.
38
-
39
- <Canvas of={DetailsStories.InCard} />
40
-
41
- ### Med fargar
42
-
43
- `Details` kan visast i fargar frå temaet ditt.
44
-
45
- <Canvas of={DetailsStories.InCardWithColor} />
46
-
47
- #### Kontrollert
48
-
49
- `Details` held sjølv styr på om den er open eller lukka, men dette kan òg kontrollerast utanfrå.
50
-
51
- <Canvas of={DetailsStories.Controlled} />
52
-
53
- ## Retningslinjer
54
-
55
- Ikkje bruk `Details` til å skjula innhald for å gjera sida "ryddigare". Når vi skjuler innhold er det fare for at brukerne ikke se innholdet i det hele tatt. Finn ut om du faktisk må skjula innhald og ver klar over kvifor du gjer det.
56
-
57
- **Passar til å**
58
-
59
- - samla innhald
60
- - gjera det frivillig å sjå innhald som er mindre viktig
61
- - visa tilleggsinformasjon som kan vera til hjelp for brukarane
62
-
63
- **Passar ikkje til å**
64
-
65
- - visa viktig innhald som alle bør sjå når dei kjem til sida
66
- - oppsummera feilmeldingar - bruk heller [`ErrorSummary`](/docs/komponenter-errorsummary--docs)
67
-
68
- ### Unngå nøsta lister
69
-
70
- Ikkje legg ein `Details` inni ein annan, det vi kallar nøsta lister. Det kan bli forvirrande for brukaren å forstå kva som er opna og lukka, spesielt når fleire nivå kan opnast samtidig.
71
-
72
- ## Tekst
73
-
74
- Sørg for at overskrifta gjev ei god skildring av kva innhaldet i `Details` er. Overskriftene kan ha stor tyding for om brukarane finn det dei treng, om innhaldet blir lese og om det kan reknast som tilgjengeleg for alle brukarar. «Vis meir» eller «Les meir her» er ikkje gode nok titlar. Har du ein `Details` med mange nedtrekk, kan du ha ei hovudoverskrift eller temaoverskrift over heile lista.
75
-
76
- Hold innholdet i `Details` kort for å sikre at det lett kan relateres til overskriften. Om innhaldet er for langt bør du fordela innhaldet i fleire `Details`. Ved veldig mykje innhald kan det vere betre å vurdere å lage eigne sider.
77
-
78
- ## Tilgjengelegheit
79
-
80
- [`Chevron`](https://aksel.nav.no/ikoner/ChevronDown)-ikonet er plassert til venstre for teksten, av omsyn til brukarar med nedsett synsfelt. Dei ser berre ein liten del av skjermen om gongen, og skannar gjerne vertikalt nedover på venstre side. Difor bør funksjonelle element stå til venstre, der dei lettare blir oppdaga. Plasseringa av chevronen bør òg vere konsekvent for å unngå forveksling med andre element.
81
-
82
- Ikkje plasser andre interaktive element inn i `Details.Summary`, då heile rada skal vera klikkbar. Ikonet og teksten skal _ikkje_ lenka til ulike handlingar (til dømes at teksten går vidare til ei side, medan ikonet opnar lista). Brukarane ventar ikkje at ikon og tekst skal gje ulikt resultat når dei vel dei.
83
-
84
- <kbd>Space</kbd> eller <kbd>Enter</kbd> åpnar eller lukkar `Details`.
85
-
86
- ## Kjende manglar
87
-
88
- I Firefox og Safari blir det ikkje spelt av animasjon når `Details` blir opna og lukka.
89
- Sjå [interpolate-size](https://developer.mozilla.org/en-US/docs/Web/CSS/interpolate-size).
@@ -1,56 +0,0 @@
1
- import { Component } from '@angular/core'
2
- import { render, screen } from '@testing-library/angular'
3
- import userEvent from '@testing-library/user-event'
4
- import { Details } from './details'
5
- import { DetailsContent } from './details-content'
6
- import { DetailsSummary } from './details-summary'
7
-
8
- describe('Details', () => {
9
- it('should have summary, content and be open when clicked', async () => {
10
- @Component({
11
- template: `
12
- <ksd-details>
13
- <ksd-details-summary>Details Summary Text</ksd-details-summary>
14
- <ksd-details-content
15
- >The fantastic details content text</ksd-details-content
16
- >
17
- </ksd-details>
18
- `,
19
- imports: [Details, DetailsContent, DetailsSummary],
20
- })
21
- class TestDetails {}
22
-
23
- await render(TestDetails)
24
-
25
- const user = userEvent.setup()
26
- const detailsExpandButton = screen.getByRole('button')
27
-
28
- await user.click(detailsExpandButton)
29
-
30
- expect(screen.getByText('Details Summary Text'))
31
- expect(screen.getByText('The fantastic details content text'))
32
- expect(detailsExpandButton).toHaveAttribute('aria-expanded', 'true')
33
- })
34
-
35
- it('should render details with open state as controlled', async () => {
36
- @Component({
37
- template: `
38
- <ksd-details [open]="true" (toggled)="noop()">
39
- <ksd-details-summary>Details Summary Text</ksd-details-summary>
40
- <ksd-details-content
41
- >The fantastic details content text</ksd-details-content
42
- >
43
- </ksd-details>
44
- `,
45
- imports: [Details, DetailsContent, DetailsSummary],
46
- })
47
- class TestDetails {
48
- noop = () => undefined
49
- }
50
-
51
- await render(TestDetails)
52
-
53
- const detailsExpandButton = screen.getByRole('button')
54
- expect(detailsExpandButton).toHaveAttribute('aria-expanded', 'true')
55
- })
56
- })
@@ -1,129 +0,0 @@
1
- import { Meta, moduleMetadata, StoryObj } from '@storybook/angular'
2
- import { Card } from '../card/card'
3
- import { ControlledDetails } from './controlled-details'
4
- import { Details } from './details'
5
- import { DetailsContent } from './details-content'
6
- import { DetailsSummary } from './details-summary'
7
-
8
- const meta: Meta<Details> = {
9
- component: Details,
10
- title: 'Komponenter/Details',
11
- parameters: {
12
- layout: 'padded',
13
- },
14
- decorators: [
15
- moduleMetadata({
16
- imports: [
17
- Card,
18
- Details,
19
- DetailsContent,
20
- DetailsSummary,
21
- ControlledDetails,
22
- ],
23
- }),
24
- ],
25
- }
26
- export default meta
27
- type Story = StoryObj<Details>
28
-
29
- export const Preview: Story = {
30
- args: {},
31
- parameters: {
32
- summaryText: 'Vedlegg',
33
- contentText: 'Vedlegg 1, vedlegg 2, vedlegg 3',
34
- },
35
- render: (args, context) => ({
36
- props: {
37
- ...args,
38
- summary: context.parameters['summaryText'],
39
- content: context.parameters['contentText'],
40
- },
41
- template: `
42
- <ksd-details data-testid="details">
43
- <ksd-details-summary>Vedlegg</ksd-details-summary>
44
- <ksd-details-content>Vedlegg 1, vedlegg 2, vedlegg 3</ksd-details-content>
45
- </ksd-details>
46
- `,
47
- }),
48
- }
49
-
50
- export const WithoutCard: Story = {
51
- render: () => ({
52
- template: `
53
- <ksd-details>
54
- <ksd-details-summary>Vedlegg</ksd-details-summary>
55
- <ksd-details-content>Vedlegg 1, vedlegg 2, vedlegg 3</ksd-details-content>
56
- </ksd-details>
57
- `,
58
- }),
59
- }
60
-
61
- export const InCard: Story = {
62
- render: () => ({
63
- template: `
64
- <article ksd-card>
65
- <ksd-details>
66
- <ksd-details-summary>Vedlegg</ksd-details-summary>
67
- <ksd-details-content>Vedlegg 1, vedlegg 2, vedlegg 3</ksd-details-content>
68
- </ksd-details>
69
- </article>
70
- `,
71
- }),
72
- }
73
-
74
- export const InCardWithColor: Story = {
75
- render: () => ({
76
- template: `
77
- <div style="display: flex; flex-direction: column; gap: 1rem">
78
- <article ksd-card>
79
- <ksd-details data-color="accent">
80
- <ksd-details-summary>Vedlegg</ksd-details-summary>
81
- <ksd-details-content>Vedlegg 1, vedlegg 2, vedlegg 3</ksd-details-content>
82
- </ksd-details>
83
- </article>
84
- <article ksd-card>
85
- <ksd-details data-color="accent" variant="tinted">
86
- <ksd-details-summary>Vedlegg</ksd-details-summary>
87
- <ksd-details-content>Vedlegg 1, vedlegg 2, vedlegg 3</ksd-details-content>
88
- </ksd-details>
89
- </article>
90
- </div>
91
- `,
92
- }),
93
- }
94
-
95
- export const WithDifferentSizes: Story = {
96
- render: () => ({
97
- template: `
98
- <ksd-details data-size="sm">
99
- <ksd-details-summary>Vedlegg</ksd-details-summary>
100
- <ksd-details-content>Vedlegg 1, vedlegg 2, vedlegg 3</ksd-details-content>
101
- </ksd-details>
102
- <ksd-details data-size="md">
103
- <ksd-details-summary>Vedlegg</ksd-details-summary>
104
- <ksd-details-content>Vedlegg 1, vedlegg 2, vedlegg 3</ksd-details-content>
105
- </ksd-details>
106
- <ksd-details data-size="lg">
107
- <ksd-details-summary>Vedlegg</ksd-details-summary>
108
- <ksd-details-content>Vedlegg 1, vedlegg 2, vedlegg 3</ksd-details-content>
109
- </ksd-details>
110
- `,
111
- }),
112
- }
113
-
114
- export const DefaultOpen: Story = {
115
- render: () => ({
116
- template: `
117
- <ksd-details [defaultOpen]="true">
118
- <ksd-details-summary>Vedlegg</ksd-details-summary>
119
- <ksd-details-content>Vedlegg 1, vedlegg 2, vedlegg 3</ksd-details-content>
120
- </ksd-details>
121
- `,
122
- }),
123
- }
124
-
125
- export const Controlled: StoryObj<ControlledDetails> = {
126
- render: () => ({
127
- template: `<fiks-controlled-details />`,
128
- }),
129
- }
@@ -1,69 +0,0 @@
1
- import {
2
- Component,
3
- CUSTOM_ELEMENTS_SCHEMA,
4
- ElementRef,
5
- input,
6
- output,
7
- viewChild,
8
- } from '@angular/core'
9
- import '@u-elements/u-details'
10
-
11
- @Component({
12
- selector: 'ksd-details',
13
- schemas: [CUSTOM_ELEMENTS_SCHEMA],
14
- template: `
15
- <u-details
16
- #detailsRef
17
- class="ds-details"
18
- [attr.data-variant]="variant()"
19
- [attr.open]="(open() ?? defaultOpen()) || undefined"
20
- [attr.data-color]="dataColor()"
21
- [attr.data-size]="dataSize()"
22
- (toggle)="onToggle($event)"
23
- >
24
- <u-summary>
25
- <ng-content select="ksd-details-summary" />
26
- </u-summary>
27
- <div>
28
- <ng-content select="ksd-details-content" />
29
- </div>
30
- </u-details>
31
- `,
32
- styles: `
33
- /* Styles needed since Designsystemet styles doesnt expect an element wrapping .ds-details, which we have */
34
- .ds-card > :host(:last-of-type) > .ds-details {
35
- border-bottom: 0;
36
- }
37
-
38
- .ds-card > :host(:first-of-type) > .ds-details {
39
- border-top: 0;
40
- }
41
-
42
- :host(:not(:first-of-type)) > .ds-details {
43
- border-top: 0;
44
- margin-top: 0;
45
- }
46
- `,
47
- })
48
- export class Details {
49
- readonly dataSize = input<'sm' | 'md' | 'lg' | undefined>(undefined, {
50
- // eslint-disable-next-line @angular-eslint/no-input-rename
51
- alias: 'data-size',
52
- })
53
- readonly dataColor = input<string | undefined>(undefined, {
54
- // eslint-disable-next-line @angular-eslint/no-input-rename
55
- alias: 'data-color',
56
- })
57
- readonly variant = input<'tinted' | 'default'>('default')
58
- readonly defaultOpen = input<boolean>(false)
59
- readonly open = input<boolean | undefined>(undefined)
60
- readonly toggled = output<Event>()
61
- private detailsRef = viewChild<ElementRef<HTMLDetailsElement>>('detailsRef')
62
-
63
- onToggle(event: Event) {
64
- const details = this.detailsRef()?.nativeElement
65
- if (details && details.open !== this.open()) {
66
- this.toggled.emit(event)
67
- }
68
- }
69
- }
@@ -1,3 +0,0 @@
1
- export { Details } from './details'
2
- export { DetailsContent } from './details-content'
3
- export { DetailsSummary } from './details-summary'
@@ -1,56 +0,0 @@
1
- import { Component, computed, effect, inject, input } from '@angular/core'
2
- import { ValidationMessage } from '../validation-message'
3
- import { FieldState } from './field-state'
4
-
5
- @Component({
6
- selector: 'ksd-field-counter',
7
- imports: [ValidationMessage],
8
- template: `
9
- <div data-field="description" class="ds-sr-only" aria-live="polite">
10
- @if (hasExceededLimit()) {
11
- {{ excessCount() }} tegn for mye
12
- }
13
- </div>
14
- @if (hasExceededLimit()) {
15
- <p ksd-validation-message>{{ excessCount() }} tegn for mye</p>
16
- } @else {
17
- <p data-field="validation">{{ remainder() }} tegn igjen</p>
18
- }
19
- `,
20
-
21
- /**
22
- * Apply custom styles here to get correct spacing because
23
- * the rendered host element from Angular is getting in the way
24
- */
25
- styles: `
26
- :host > * {
27
- margin-top: var(--dsc-field-content-spacing);
28
- }
29
- `,
30
- })
31
- export class FieldCounter {
32
- /**
33
- * The maximum allowed characters.
34
- *
35
- **/
36
- readonly limit = input.required<number>()
37
-
38
- /**
39
- * How many characters have been typed.
40
- *
41
- **/
42
- readonly count = input.required<number>()
43
- protected readonly remainder = computed(() => this.limit() - this.count())
44
- protected readonly excessCount = computed(() => Math.abs(this.remainder()))
45
- protected readonly hasExceededLimit = computed(
46
- () => this.count() > this.limit(),
47
- )
48
-
49
- private fieldState = inject(FieldState)
50
-
51
- constructor() {
52
- effect(() => {
53
- this.fieldState.hasExceededCounter.set(this.hasExceededLimit())
54
- })
55
- }
56
- }
@@ -1,10 +0,0 @@
1
- import { Component } from '@angular/core'
2
-
3
- @Component({
4
- selector: '[ksd-field-description]',
5
- host: {
6
- 'data-field': 'description',
7
- },
8
- template: `<ng-content />`,
9
- })
10
- export class FieldDescription {}
@@ -1,13 +0,0 @@
1
- import { Component } from '@angular/core'
2
- import { ValidationMessage } from '../validation-message'
3
-
4
- @Component({
5
- selector: '[ksd-error]',
6
- template: `<ng-content />`,
7
- hostDirectives: [
8
- {
9
- directive: ValidationMessage,
10
- },
11
- ],
12
- })
13
- export class FieldError {}
@@ -1,121 +0,0 @@
1
- /**
2
- * Lifted from Designsystemet core repo.
3
- * Takes care of binding ids, labels and aria-describedby attributes
4
- *
5
- * @param fieldElement - The field element to observe
6
- * @returns A function to disconnect the observer
7
- * */
8
- export function fieldObserver(fieldElement: HTMLElement | null) {
9
- if (!fieldElement) return
10
-
11
- const elements = new Map<Element, string | null>()
12
- const typeCounter = new Map<string, number>() // Track count for each data-field type
13
- const uuid = `:${Date.now().toString(36)}${Math.random().toString(36).slice(2, 5)}`
14
- let input: Element | null = null
15
- let describedby = ''
16
-
17
- const process = (mutations: Partial<MutationRecord>[]) => {
18
- const changed: Node[] = []
19
- const removed: Node[] = []
20
-
21
- // Merge MutationRecords
22
- for (const mutation of mutations) {
23
- if (mutation.attributeName) changed.push(mutation.target ?? fieldElement)
24
- // @ts-expect-error - addedNodes is not typed
25
- changed.push(...(mutation.addedNodes || []))
26
- removed.push(...(mutation.removedNodes || []))
27
- }
28
-
29
- // Register elements
30
- for (const el of changed) {
31
- if (!isElement(el)) continue
32
-
33
- if (isLabel(el)) elements.set(el, el.htmlFor)
34
- else if (el.hasAttribute('data-field')) elements.set(el, el.id)
35
- else if (isInputLike(el)) {
36
- input = el
37
- describedby = el.getAttribute('aria-describedby') || ''
38
- }
39
- }
40
-
41
- // Reset removed elements
42
- for (const el of removed) {
43
- if (!isElement(el)) continue
44
-
45
- if (input === el) input = null
46
- if (elements.has(el)) {
47
- setAttr(el, isLabel(el) ? 'for' : 'id', elements.get(el))
48
- elements.delete(el)
49
- }
50
- }
51
-
52
- // Connect elements
53
- const describedbyIds = [describedby] // Keep original aria-describedby
54
- const inputId = input?.id || uuid
55
-
56
- // Reset type counters since we reprocess all elements
57
- typeCounter.clear()
58
-
59
- for (const [el, value] of elements) {
60
- const descriptionType = el.getAttribute('data-field')
61
- let id: string
62
-
63
- if (descriptionType) {
64
- // Increment type counter for this type
65
- const count = (typeCounter.get(descriptionType) || 0) + 1
66
- typeCounter.set(descriptionType, count)
67
- id = `${inputId}:${descriptionType}:${count}`
68
- } else {
69
- id = inputId
70
- }
71
-
72
- if (!value) setAttr(el, isLabel(el) ? 'for' : 'id', id) // Ensure we have a value
73
- if (descriptionType === 'validation')
74
- describedbyIds.unshift(el.id) // Validations to the front
75
- else if (descriptionType) describedbyIds.push(el.id) // Other descriptions to the back
76
- }
77
-
78
- setAttr(input, 'id', inputId)
79
- setAttr(input, 'aria-describedby', describedbyIds.join(' ').trim())
80
- }
81
-
82
- const observer = createOptimizedMutationObserver(process)
83
- observer.observe(fieldElement, {
84
- attributeFilter: ['id', 'for', 'aria-describedby'],
85
- attributes: true,
86
- childList: true,
87
- subtree: true,
88
- })
89
-
90
- process([{ addedNodes: fieldElement.querySelectorAll('*') }]) // Initial setup
91
- observer.takeRecords() // Clear initial setup queue
92
- return () => observer.disconnect()
93
- }
94
-
95
- // Utilities
96
- export const isElement = (node: Node) => node instanceof Element
97
- export const isLabel = (node: Node) => node instanceof HTMLLabelElement
98
- export const isInputLike = (node: unknown): node is HTMLInputElement =>
99
- node instanceof HTMLElement &&
100
- 'validity' in node &&
101
- !(node instanceof HTMLButtonElement) // Matches input, textarea, select and form accosiated custom elements
102
-
103
- const setAttr = (el: Element | null, name: string, value?: string | null) =>
104
- value ? el?.setAttribute(name, value) : el?.removeAttribute(name)
105
-
106
- // Speed up MutationObserver by debouncing, clearing internal queue after changes and only running when page is visible
107
- function createOptimizedMutationObserver(callback: MutationCallback) {
108
- const queue: MutationRecord[] = []
109
- const observer = new MutationObserver((mutations) => {
110
- if (!queue.length) requestAnimationFrame(process)
111
- queue.push(...mutations)
112
- })
113
-
114
- const process = () => {
115
- callback(queue, observer)
116
- queue.length = 0 // Reset queue
117
- observer.takeRecords() // Clear queue due to DOM changes in callback
118
- }
119
-
120
- return observer
121
- }
@@ -1,21 +0,0 @@
1
- import { computed, Injectable, signal } from '@angular/core'
2
-
3
- @Injectable()
4
- export class FieldState {
5
- /**
6
- * Whether the field counter has exceeded its limit
7
- */
8
- hasExceededCounter = signal(false)
9
-
10
- /**
11
- * Whether the field has errors projected from the outside
12
- */
13
- hasProjectedErrors = signal(false)
14
-
15
- /**
16
- * Whether the field has any errors associated with it
17
- */
18
- hasError = computed(
19
- () => this.hasExceededCounter() || this.hasProjectedErrors(),
20
- )
21
- }