govuk_publishing_components 58.1.1 → 59.0.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 (165) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/images/select-with-search/cross-icon.svg +6 -0
  3. data/app/assets/javascripts/govuk_publishing_components/analytics-ga4/ga4-search-tracker.js +4 -0
  4. data/app/assets/javascripts/govuk_publishing_components/components/select-with-search.js +57 -0
  5. data/app/assets/stylesheets/govuk_publishing_components/_all_components.scss +2 -0
  6. data/app/assets/stylesheets/govuk_publishing_components/components/_action-link.scss +14 -136
  7. data/app/assets/stylesheets/govuk_publishing_components/components/_heading.scss +0 -5
  8. data/app/assets/stylesheets/govuk_publishing_components/components/_layout-header.scss +6 -48
  9. data/app/assets/stylesheets/govuk_publishing_components/components/_select-with-search.scss +168 -0
  10. data/app/assets/stylesheets/govuk_publishing_components/components/_select.scss +6 -0
  11. data/app/assets/stylesheets/govuk_publishing_components/components/_tag.scss +14 -0
  12. data/app/assets/stylesheets/govuk_publishing_components/components/helpers/_markdown-typography.scss +1 -1
  13. data/app/views/govuk_publishing_components/components/_action_link.html.erb +4 -37
  14. data/app/views/govuk_publishing_components/components/_layout_header.html.erb +0 -2
  15. data/app/views/govuk_publishing_components/components/_select.html.erb +22 -23
  16. data/app/views/govuk_publishing_components/components/_select_with_search.html.erb +14 -0
  17. data/app/views/govuk_publishing_components/components/_share_links.html.erb +17 -9
  18. data/app/views/govuk_publishing_components/components/_tag.html.erb +20 -0
  19. data/app/views/govuk_publishing_components/components/docs/action_link.yml +0 -62
  20. data/app/views/govuk_publishing_components/components/docs/layout_header.yml +13 -0
  21. data/app/views/govuk_publishing_components/components/docs/select.yml +11 -0
  22. data/app/views/govuk_publishing_components/components/docs/select_with_search.yml +196 -0
  23. data/app/views/govuk_publishing_components/components/docs/share_links.yml +10 -0
  24. data/app/views/govuk_publishing_components/components/docs/signup_link.yml +0 -7
  25. data/app/views/govuk_publishing_components/components/docs/single_page_notification_button.yml +1 -7
  26. data/app/views/govuk_publishing_components/components/docs/subscription_links.yml +1 -7
  27. data/app/views/govuk_publishing_components/components/docs/tag.yml +57 -0
  28. data/app/views/govuk_publishing_components/components/layout_header/_header_logo.html.erb +16 -5
  29. data/lib/govuk_publishing_components/presenters/heading_helper.rb +1 -2
  30. data/lib/govuk_publishing_components/presenters/select_helper.rb +8 -5
  31. data/lib/govuk_publishing_components/presenters/select_with_search_helper.rb +92 -0
  32. data/lib/govuk_publishing_components/presenters/single_page_notification_button_helper.rb +1 -1
  33. data/lib/govuk_publishing_components/version.rb +1 -1
  34. data/lib/govuk_publishing_components.rb +1 -0
  35. data/node_modules/choices.js/LICENSE +21 -0
  36. data/node_modules/choices.js/README.md +1360 -0
  37. data/node_modules/choices.js/package.json +173 -0
  38. data/node_modules/choices.js/public/assets/scripts/choices.js +5230 -0
  39. data/node_modules/choices.js/public/assets/scripts/choices.min.js +2 -0
  40. data/node_modules/choices.js/public/assets/scripts/choices.mjs +5222 -0
  41. data/node_modules/choices.js/public/assets/scripts/choices.search-basic.js +4748 -0
  42. data/node_modules/choices.js/public/assets/scripts/choices.search-basic.min.js +2 -0
  43. data/node_modules/choices.js/public/assets/scripts/choices.search-basic.mjs +4740 -0
  44. data/node_modules/choices.js/public/assets/scripts/choices.search-kmp.js +3631 -0
  45. data/node_modules/choices.js/public/assets/scripts/choices.search-kmp.min.js +2 -0
  46. data/node_modules/choices.js/public/assets/scripts/choices.search-kmp.mjs +3623 -0
  47. data/node_modules/choices.js/public/assets/scripts/choices.search-prefix.js +3590 -0
  48. data/node_modules/choices.js/public/assets/scripts/choices.search-prefix.min.js +2 -0
  49. data/node_modules/choices.js/public/assets/scripts/choices.search-prefix.mjs +3582 -0
  50. data/node_modules/choices.js/public/assets/styles/base.css +180 -0
  51. data/node_modules/choices.js/public/assets/styles/base.css.map +1 -0
  52. data/node_modules/choices.js/public/assets/styles/base.min.css +1 -0
  53. data/node_modules/choices.js/public/assets/styles/choices.css +338 -0
  54. data/node_modules/choices.js/public/assets/styles/choices.css.map +1 -0
  55. data/node_modules/choices.js/public/assets/styles/choices.min.css +1 -0
  56. data/node_modules/choices.js/public/types/src/index.d.ts +6 -0
  57. data/node_modules/choices.js/public/types/src/scripts/actions/choices.d.ts +30 -0
  58. data/node_modules/choices.js/public/types/src/scripts/actions/groups.d.ts +8 -0
  59. data/node_modules/choices.js/public/types/src/scripts/actions/items.d.ts +17 -0
  60. data/node_modules/choices.js/public/types/src/scripts/choices.d.ts +210 -0
  61. data/node_modules/choices.js/public/types/src/scripts/components/container.d.ts +36 -0
  62. data/node_modules/choices.js/public/types/src/scripts/components/dropdown.d.ts +21 -0
  63. data/node_modules/choices.js/public/types/src/scripts/components/index.d.ts +7 -0
  64. data/node_modules/choices.js/public/types/src/scripts/components/input.d.ts +37 -0
  65. data/node_modules/choices.js/public/types/src/scripts/components/list.d.ts +14 -0
  66. data/node_modules/choices.js/public/types/src/scripts/components/wrapped-element.d.ts +21 -0
  67. data/node_modules/choices.js/public/types/src/scripts/components/wrapped-input.d.ts +3 -0
  68. data/node_modules/choices.js/public/types/src/scripts/components/wrapped-select.d.ts +20 -0
  69. data/node_modules/choices.js/public/types/src/scripts/constants.d.ts +1 -0
  70. data/node_modules/choices.js/public/types/src/scripts/defaults.d.ts +4 -0
  71. data/node_modules/choices.js/public/types/src/scripts/interfaces/action-type.d.ts +13 -0
  72. data/node_modules/choices.js/public/types/src/scripts/interfaces/build-flags.d.ts +11 -0
  73. data/node_modules/choices.js/public/types/src/scripts/interfaces/choice-full.d.ts +23 -0
  74. data/node_modules/choices.js/public/types/src/scripts/interfaces/class-names.d.ts +61 -0
  75. data/node_modules/choices.js/public/types/src/scripts/interfaces/event-choice.d.ts +7 -0
  76. data/node_modules/choices.js/public/types/src/scripts/interfaces/event-type.d.ts +14 -0
  77. data/node_modules/choices.js/public/types/src/scripts/interfaces/group-full.d.ts +10 -0
  78. data/node_modules/choices.js/public/types/src/scripts/interfaces/index.d.ts +14 -0
  79. data/node_modules/choices.js/public/types/src/scripts/interfaces/input-choice.d.ts +15 -0
  80. data/node_modules/choices.js/public/types/src/scripts/interfaces/input-group.d.ts +10 -0
  81. data/node_modules/choices.js/public/types/src/scripts/interfaces/item.d.ts +17 -0
  82. data/node_modules/choices.js/public/types/src/scripts/interfaces/keycode-map.d.ts +13 -0
  83. data/node_modules/choices.js/public/types/src/scripts/interfaces/options.d.ts +566 -0
  84. data/node_modules/choices.js/public/types/src/scripts/interfaces/passed-element-type.d.ts +7 -0
  85. data/node_modules/choices.js/public/types/src/scripts/interfaces/passed-element.d.ts +95 -0
  86. data/node_modules/choices.js/public/types/src/scripts/interfaces/position-options-type.d.ts +1 -0
  87. data/node_modules/choices.js/public/types/src/scripts/interfaces/search.d.ts +11 -0
  88. data/node_modules/choices.js/public/types/src/scripts/interfaces/state.d.ts +10 -0
  89. data/node_modules/choices.js/public/types/src/scripts/interfaces/store.d.ts +64 -0
  90. data/node_modules/choices.js/public/types/src/scripts/interfaces/string-pre-escaped.d.ts +3 -0
  91. data/node_modules/choices.js/public/types/src/scripts/interfaces/string-untrusted.d.ts +4 -0
  92. data/node_modules/choices.js/public/types/src/scripts/interfaces/templates.d.ts +29 -0
  93. data/node_modules/choices.js/public/types/src/scripts/interfaces/types.d.ts +18 -0
  94. data/node_modules/choices.js/public/types/src/scripts/lib/choice-input.d.ts +9 -0
  95. data/node_modules/choices.js/public/types/src/scripts/lib/html-guard-statements.d.ts +4 -0
  96. data/node_modules/choices.js/public/types/src/scripts/lib/utils.d.ts +31 -0
  97. data/node_modules/choices.js/public/types/src/scripts/reducers/choices.d.ts +8 -0
  98. data/node_modules/choices.js/public/types/src/scripts/reducers/groups.d.ts +8 -0
  99. data/node_modules/choices.js/public/types/src/scripts/reducers/items.d.ts +9 -0
  100. data/node_modules/choices.js/public/types/src/scripts/search/fuse.d.ts +14 -0
  101. data/node_modules/choices.js/public/types/src/scripts/search/index.d.ts +3 -0
  102. data/node_modules/choices.js/public/types/src/scripts/search/kmp.d.ts +11 -0
  103. data/node_modules/choices.js/public/types/src/scripts/search/prefix-filter.d.ts +11 -0
  104. data/node_modules/choices.js/public/types/src/scripts/store/store.d.ts +59 -0
  105. data/node_modules/choices.js/public/types/src/scripts/templates.d.ts +8 -0
  106. data/node_modules/choices.js/src/entry.js +3 -0
  107. data/node_modules/choices.js/src/icons/cross-inverse.svg +1 -0
  108. data/node_modules/choices.js/src/icons/cross.svg +1 -0
  109. data/node_modules/choices.js/src/index.ts +8 -0
  110. data/node_modules/choices.js/src/scripts/actions/choices.ts +59 -0
  111. data/node_modules/choices.js/src/scripts/actions/groups.ts +14 -0
  112. data/node_modules/choices.js/src/scripts/actions/items.ts +34 -0
  113. data/node_modules/choices.js/src/scripts/choices.ts +2364 -0
  114. data/node_modules/choices.js/src/scripts/components/container.ts +157 -0
  115. data/node_modules/choices.js/src/scripts/components/dropdown.ts +50 -0
  116. data/node_modules/choices.js/src/scripts/components/index.ts +8 -0
  117. data/node_modules/choices.js/src/scripts/components/input.ts +146 -0
  118. data/node_modules/choices.js/src/scripts/components/list.ts +89 -0
  119. data/node_modules/choices.js/src/scripts/components/wrapped-element.ts +89 -0
  120. data/node_modules/choices.js/src/scripts/components/wrapped-input.ts +3 -0
  121. data/node_modules/choices.js/src/scripts/components/wrapped-select.ts +115 -0
  122. data/node_modules/choices.js/src/scripts/constants.ts +1 -0
  123. data/node_modules/choices.js/src/scripts/defaults.ts +93 -0
  124. data/node_modules/choices.js/src/scripts/interfaces/action-type.ts +15 -0
  125. data/node_modules/choices.js/src/scripts/interfaces/build-flags.ts +17 -0
  126. data/node_modules/choices.js/src/scripts/interfaces/choice-full.ts +30 -0
  127. data/node_modules/choices.js/src/scripts/interfaces/class-names.ts +61 -0
  128. data/node_modules/choices.js/src/scripts/interfaces/event-choice.ts +9 -0
  129. data/node_modules/choices.js/src/scripts/interfaces/event-type.ts +16 -0
  130. data/node_modules/choices.js/src/scripts/interfaces/group-full.ts +12 -0
  131. data/node_modules/choices.js/src/scripts/interfaces/index.ts +14 -0
  132. data/node_modules/choices.js/src/scripts/interfaces/input-choice.ts +17 -0
  133. data/node_modules/choices.js/src/scripts/interfaces/input-group.ts +11 -0
  134. data/node_modules/choices.js/src/scripts/interfaces/item.ts +17 -0
  135. data/node_modules/choices.js/src/scripts/interfaces/keycode-map.ts +13 -0
  136. data/node_modules/choices.js/src/scripts/interfaces/options.ts +619 -0
  137. data/node_modules/choices.js/src/scripts/interfaces/passed-element-type.ts +9 -0
  138. data/node_modules/choices.js/src/scripts/interfaces/passed-element.ts +96 -0
  139. data/node_modules/choices.js/src/scripts/interfaces/position-options-type.ts +1 -0
  140. data/node_modules/choices.js/src/scripts/interfaces/search.ts +12 -0
  141. data/node_modules/choices.js/src/scripts/interfaces/state.ts +12 -0
  142. data/node_modules/choices.js/src/scripts/interfaces/store.ts +84 -0
  143. data/node_modules/choices.js/src/scripts/interfaces/string-pre-escaped.ts +3 -0
  144. data/node_modules/choices.js/src/scripts/interfaces/string-untrusted.ts +5 -0
  145. data/node_modules/choices.js/src/scripts/interfaces/templates.ts +66 -0
  146. data/node_modules/choices.js/src/scripts/interfaces/types.ts +21 -0
  147. data/node_modules/choices.js/src/scripts/lib/choice-input.ts +88 -0
  148. data/node_modules/choices.js/src/scripts/lib/html-guard-statements.ts +7 -0
  149. data/node_modules/choices.js/src/scripts/lib/utils.ts +230 -0
  150. data/node_modules/choices.js/src/scripts/reducers/choices.ts +86 -0
  151. data/node_modules/choices.js/src/scripts/reducers/groups.ts +32 -0
  152. data/node_modules/choices.js/src/scripts/reducers/items.ts +86 -0
  153. data/node_modules/choices.js/src/scripts/search/fuse.ts +59 -0
  154. data/node_modules/choices.js/src/scripts/search/index.ts +17 -0
  155. data/node_modules/choices.js/src/scripts/search/kmp.ts +87 -0
  156. data/node_modules/choices.js/src/scripts/search/prefix-filter.ts +42 -0
  157. data/node_modules/choices.js/src/scripts/store/store.ts +184 -0
  158. data/node_modules/choices.js/src/scripts/templates.ts +409 -0
  159. data/node_modules/choices.js/src/styles/base.scss +189 -0
  160. data/node_modules/choices.js/src/styles/choices.scss +414 -0
  161. data/node_modules/choices.js/src/tsconfig.json +22 -0
  162. metadata +137 -4
  163. data/app/assets/images/govuk_publishing_components/action-link-arrow--dark.svg +0 -5
  164. data/app/assets/images/govuk_publishing_components/action-link-arrow--simple-light.svg +0 -4
  165. data/app/assets/images/govuk_publishing_components/action-link-arrow.svg +0 -5
@@ -0,0 +1,157 @@
1
+ import { addClassesToElement, removeClassesFromElement } from '../lib/utils';
2
+ import { ClassNames } from '../interfaces/class-names';
3
+ import { PositionOptionsType } from '../interfaces/position-options-type';
4
+ import { PassedElementType, PassedElementTypes } from '../interfaces/passed-element-type';
5
+
6
+ export default class Container {
7
+ element: HTMLElement;
8
+
9
+ type: PassedElementType;
10
+
11
+ classNames: ClassNames;
12
+
13
+ position: PositionOptionsType;
14
+
15
+ isOpen: boolean;
16
+
17
+ isFlipped: boolean;
18
+
19
+ isDisabled: boolean;
20
+
21
+ isLoading: boolean;
22
+
23
+ constructor({
24
+ element,
25
+ type,
26
+ classNames,
27
+ position,
28
+ }: {
29
+ element: HTMLElement;
30
+ type: PassedElementType;
31
+ classNames: ClassNames;
32
+ position: PositionOptionsType;
33
+ }) {
34
+ this.element = element;
35
+ this.classNames = classNames;
36
+ this.type = type;
37
+ this.position = position;
38
+ this.isOpen = false;
39
+ this.isFlipped = false;
40
+ this.isDisabled = false;
41
+ this.isLoading = false;
42
+ }
43
+
44
+ /**
45
+ * Determine whether container should be flipped based on passed
46
+ * dropdown position
47
+ */
48
+ shouldFlip(dropdownPos: number, dropdownHeight: number): boolean {
49
+ // If flip is enabled and the dropdown bottom position is
50
+ // greater than the window height flip the dropdown.
51
+ let shouldFlip = false;
52
+ if (this.position === 'auto') {
53
+ shouldFlip =
54
+ this.element.getBoundingClientRect().top - dropdownHeight >= 0 &&
55
+ !window.matchMedia(`(min-height: ${dropdownPos + 1}px)`).matches;
56
+ } else if (this.position === 'top') {
57
+ shouldFlip = true;
58
+ }
59
+
60
+ return shouldFlip;
61
+ }
62
+
63
+ setActiveDescendant(activeDescendantID: string): void {
64
+ this.element.setAttribute('aria-activedescendant', activeDescendantID);
65
+ }
66
+
67
+ removeActiveDescendant(): void {
68
+ this.element.removeAttribute('aria-activedescendant');
69
+ }
70
+
71
+ open(dropdownPos: number, dropdownHeight: number): void {
72
+ addClassesToElement(this.element, this.classNames.openState);
73
+ this.element.setAttribute('aria-expanded', 'true');
74
+ this.isOpen = true;
75
+
76
+ if (this.shouldFlip(dropdownPos, dropdownHeight)) {
77
+ addClassesToElement(this.element, this.classNames.flippedState);
78
+ this.isFlipped = true;
79
+ }
80
+ }
81
+
82
+ close(): void {
83
+ removeClassesFromElement(this.element, this.classNames.openState);
84
+ this.element.setAttribute('aria-expanded', 'false');
85
+ this.removeActiveDescendant();
86
+ this.isOpen = false;
87
+
88
+ // A dropdown flips if it does not have space within the page
89
+ if (this.isFlipped) {
90
+ removeClassesFromElement(this.element, this.classNames.flippedState);
91
+ this.isFlipped = false;
92
+ }
93
+ }
94
+
95
+ addFocusState(): void {
96
+ addClassesToElement(this.element, this.classNames.focusState);
97
+ }
98
+
99
+ removeFocusState(): void {
100
+ removeClassesFromElement(this.element, this.classNames.focusState);
101
+ }
102
+
103
+ enable(): void {
104
+ removeClassesFromElement(this.element, this.classNames.disabledState);
105
+ this.element.removeAttribute('aria-disabled');
106
+ if (this.type === PassedElementTypes.SelectOne) {
107
+ this.element.setAttribute('tabindex', '0');
108
+ }
109
+ this.isDisabled = false;
110
+ }
111
+
112
+ disable(): void {
113
+ addClassesToElement(this.element, this.classNames.disabledState);
114
+ this.element.setAttribute('aria-disabled', 'true');
115
+ if (this.type === PassedElementTypes.SelectOne) {
116
+ this.element.setAttribute('tabindex', '-1');
117
+ }
118
+ this.isDisabled = true;
119
+ }
120
+
121
+ wrap(element: HTMLElement): void {
122
+ const el = this.element;
123
+ const { parentNode } = element;
124
+ if (parentNode) {
125
+ if (element.nextSibling) {
126
+ parentNode.insertBefore(el, element.nextSibling);
127
+ } else {
128
+ parentNode.appendChild(el);
129
+ }
130
+ }
131
+
132
+ el.appendChild(element);
133
+ }
134
+
135
+ unwrap(element: HTMLElement): void {
136
+ const el = this.element;
137
+ const { parentNode } = el;
138
+ if (parentNode) {
139
+ // Move passed element outside this element
140
+ parentNode.insertBefore(element, el);
141
+ // Remove this element
142
+ parentNode.removeChild(el);
143
+ }
144
+ }
145
+
146
+ addLoadingState(): void {
147
+ addClassesToElement(this.element, this.classNames.loadingState);
148
+ this.element.setAttribute('aria-busy', 'true');
149
+ this.isLoading = true;
150
+ }
151
+
152
+ removeLoadingState(): void {
153
+ removeClassesFromElement(this.element, this.classNames.loadingState);
154
+ this.element.removeAttribute('aria-busy');
155
+ this.isLoading = false;
156
+ }
157
+ }
@@ -0,0 +1,50 @@
1
+ import { ClassNames } from '../interfaces/class-names';
2
+ import { PassedElementType } from '../interfaces/passed-element-type';
3
+ import { addClassesToElement, removeClassesFromElement } from '../lib/utils';
4
+
5
+ export default class Dropdown {
6
+ element: HTMLElement;
7
+
8
+ type: PassedElementType;
9
+
10
+ classNames: ClassNames;
11
+
12
+ isActive: boolean;
13
+
14
+ constructor({
15
+ element,
16
+ type,
17
+ classNames,
18
+ }: {
19
+ element: HTMLElement;
20
+ type: PassedElementType;
21
+ classNames: ClassNames;
22
+ }) {
23
+ this.element = element;
24
+ this.classNames = classNames;
25
+ this.type = type;
26
+ this.isActive = false;
27
+ }
28
+
29
+ /**
30
+ * Show dropdown to user by adding active state class
31
+ */
32
+ show(): this {
33
+ addClassesToElement(this.element, this.classNames.activeState);
34
+ this.element.setAttribute('aria-expanded', 'true');
35
+ this.isActive = true;
36
+
37
+ return this;
38
+ }
39
+
40
+ /**
41
+ * Hide dropdown from user
42
+ */
43
+ hide(): this {
44
+ removeClassesFromElement(this.element, this.classNames.activeState);
45
+ this.element.setAttribute('aria-expanded', 'false');
46
+ this.isActive = false;
47
+
48
+ return this;
49
+ }
50
+ }
@@ -0,0 +1,8 @@
1
+ import Dropdown from './dropdown';
2
+ import Container from './container';
3
+ import Input from './input';
4
+ import List from './list';
5
+ import WrappedInput from './wrapped-input';
6
+ import WrappedSelect from './wrapped-select';
7
+
8
+ export { Dropdown, Container, Input, List, WrappedInput, WrappedSelect };
@@ -0,0 +1,146 @@
1
+ import { ClassNames } from '../interfaces/class-names';
2
+ import { PassedElementType, PassedElementTypes } from '../interfaces/passed-element-type';
3
+
4
+ export default class Input {
5
+ element: HTMLInputElement;
6
+
7
+ type: PassedElementType;
8
+
9
+ classNames: ClassNames;
10
+
11
+ preventPaste: boolean;
12
+
13
+ isFocussed: boolean;
14
+
15
+ isDisabled: boolean;
16
+
17
+ constructor({
18
+ element,
19
+ type,
20
+ classNames,
21
+ preventPaste,
22
+ }: {
23
+ element: HTMLInputElement;
24
+ type: PassedElementType;
25
+ classNames: ClassNames;
26
+ preventPaste: boolean;
27
+ }) {
28
+ this.element = element;
29
+ this.type = type;
30
+ this.classNames = classNames;
31
+ this.preventPaste = preventPaste;
32
+
33
+ this.isFocussed = this.element.isEqualNode(document.activeElement);
34
+ this.isDisabled = element.disabled;
35
+ this._onPaste = this._onPaste.bind(this);
36
+ this._onInput = this._onInput.bind(this);
37
+ this._onFocus = this._onFocus.bind(this);
38
+ this._onBlur = this._onBlur.bind(this);
39
+ }
40
+
41
+ set placeholder(placeholder: string) {
42
+ this.element.placeholder = placeholder;
43
+ }
44
+
45
+ get value(): string {
46
+ return this.element.value;
47
+ }
48
+
49
+ set value(value: string) {
50
+ this.element.value = value;
51
+ }
52
+
53
+ addEventListeners(): void {
54
+ const el = this.element;
55
+ el.addEventListener('paste', this._onPaste);
56
+ el.addEventListener('input', this._onInput, {
57
+ passive: true,
58
+ });
59
+ el.addEventListener('focus', this._onFocus, {
60
+ passive: true,
61
+ });
62
+ el.addEventListener('blur', this._onBlur, {
63
+ passive: true,
64
+ });
65
+ }
66
+
67
+ removeEventListeners(): void {
68
+ const el = this.element;
69
+ el.removeEventListener('input', this._onInput);
70
+ el.removeEventListener('paste', this._onPaste);
71
+ el.removeEventListener('focus', this._onFocus);
72
+ el.removeEventListener('blur', this._onBlur);
73
+ }
74
+
75
+ enable(): void {
76
+ const el = this.element;
77
+ el.removeAttribute('disabled');
78
+ this.isDisabled = false;
79
+ }
80
+
81
+ disable(): void {
82
+ const el = this.element;
83
+ el.setAttribute('disabled', '');
84
+ this.isDisabled = true;
85
+ }
86
+
87
+ focus(): void {
88
+ if (!this.isFocussed) {
89
+ this.element.focus();
90
+ }
91
+ }
92
+
93
+ blur(): void {
94
+ if (this.isFocussed) {
95
+ this.element.blur();
96
+ }
97
+ }
98
+
99
+ clear(setWidth = true): this {
100
+ this.element.value = '';
101
+ if (setWidth) {
102
+ this.setWidth();
103
+ }
104
+
105
+ return this;
106
+ }
107
+
108
+ /**
109
+ * Set the correct input width based on placeholder
110
+ * value or input value
111
+ */
112
+ setWidth(): void {
113
+ // Resize input to contents or placeholder
114
+ const { element } = this;
115
+ element.style.minWidth = `${element.placeholder.length + 1}ch`;
116
+ element.style.width = `${element.value.length + 1}ch`;
117
+ }
118
+
119
+ setActiveDescendant(activeDescendantID: string): void {
120
+ this.element.setAttribute('aria-activedescendant', activeDescendantID);
121
+ }
122
+
123
+ removeActiveDescendant(): void {
124
+ this.element.removeAttribute('aria-activedescendant');
125
+ }
126
+
127
+ _onInput(): void {
128
+ if (this.type !== PassedElementTypes.SelectOne) {
129
+ this.setWidth();
130
+ }
131
+ }
132
+
133
+ _onPaste(event: ClipboardEvent): void {
134
+ if (this.preventPaste) {
135
+ event.preventDefault();
136
+ }
137
+ }
138
+
139
+ _onFocus(): void {
140
+ this.isFocussed = true;
141
+ }
142
+
143
+ _onBlur(): void {
144
+ this.isFocussed = false;
145
+ }
146
+ }
@@ -0,0 +1,89 @@
1
+ import { SCROLLING_SPEED } from '../constants';
2
+
3
+ export default class List {
4
+ element: HTMLElement;
5
+
6
+ scrollPos: number;
7
+
8
+ height: number;
9
+
10
+ constructor({ element }: { element: HTMLElement }) {
11
+ this.element = element;
12
+ this.scrollPos = this.element.scrollTop;
13
+ this.height = this.element.offsetHeight;
14
+ }
15
+
16
+ prepend(node: Element | DocumentFragment): void {
17
+ const child = this.element.firstElementChild;
18
+ if (child) {
19
+ this.element.insertBefore(node, child);
20
+ } else {
21
+ this.element.append(node);
22
+ }
23
+ }
24
+
25
+ scrollToTop(): void {
26
+ this.element.scrollTop = 0;
27
+ }
28
+
29
+ scrollToChildElement(element: HTMLElement, direction: 1 | -1): void {
30
+ if (!element) {
31
+ return;
32
+ }
33
+
34
+ const listHeight = this.element.offsetHeight;
35
+ // Scroll position of dropdown
36
+ const listScrollPosition = this.element.scrollTop + listHeight;
37
+
38
+ const elementHeight = element.offsetHeight;
39
+ // Distance from bottom of element to top of parent
40
+ const elementPos = element.offsetTop + elementHeight;
41
+
42
+ // Difference between the element and scroll position
43
+ const destination = direction > 0 ? this.element.scrollTop + elementPos - listScrollPosition : element.offsetTop;
44
+
45
+ requestAnimationFrame(() => {
46
+ this._animateScroll(destination, direction);
47
+ });
48
+ }
49
+
50
+ _scrollDown(scrollPos: number, strength: number, destination: number): void {
51
+ const easing = (destination - scrollPos) / strength;
52
+ const distance = easing > 1 ? easing : 1;
53
+
54
+ this.element.scrollTop = scrollPos + distance;
55
+ }
56
+
57
+ _scrollUp(scrollPos: number, strength: number, destination: number): void {
58
+ const easing = (scrollPos - destination) / strength;
59
+ const distance = easing > 1 ? easing : 1;
60
+
61
+ this.element.scrollTop = scrollPos - distance;
62
+ }
63
+
64
+ _animateScroll(destination: number, direction: number): void {
65
+ const strength = SCROLLING_SPEED;
66
+ const choiceListScrollTop = this.element.scrollTop;
67
+ let continueAnimation = false;
68
+
69
+ if (direction > 0) {
70
+ this._scrollDown(choiceListScrollTop, strength, destination);
71
+
72
+ if (choiceListScrollTop < destination) {
73
+ continueAnimation = true;
74
+ }
75
+ } else {
76
+ this._scrollUp(choiceListScrollTop, strength, destination);
77
+
78
+ if (choiceListScrollTop > destination) {
79
+ continueAnimation = true;
80
+ }
81
+ }
82
+
83
+ if (continueAnimation) {
84
+ requestAnimationFrame(() => {
85
+ this._animateScroll(destination, direction);
86
+ });
87
+ }
88
+ }
89
+ }
@@ -0,0 +1,89 @@
1
+ import { ClassNames } from '../interfaces/class-names';
2
+ import { EventTypes } from '../interfaces/event-type';
3
+ import { addClassesToElement, dispatchEvent, removeClassesFromElement } from '../lib/utils';
4
+ import { EventMap } from '../interfaces';
5
+
6
+ export default class WrappedElement<T extends HTMLInputElement | HTMLSelectElement> {
7
+ element: T;
8
+
9
+ classNames: ClassNames;
10
+
11
+ isDisabled: boolean;
12
+
13
+ constructor({ element, classNames }) {
14
+ this.element = element;
15
+ this.classNames = classNames;
16
+ this.isDisabled = false;
17
+ }
18
+
19
+ get isActive(): boolean {
20
+ return this.element.dataset.choice === 'active';
21
+ }
22
+
23
+ get dir(): string {
24
+ return this.element.dir;
25
+ }
26
+
27
+ get value(): string {
28
+ return this.element.value;
29
+ }
30
+
31
+ set value(value: string) {
32
+ this.element.setAttribute('value', value);
33
+ this.element.value = value;
34
+ }
35
+
36
+ conceal(): void {
37
+ const el = this.element;
38
+ // Hide passed input
39
+ addClassesToElement(el, this.classNames.input);
40
+ el.hidden = true;
41
+
42
+ // Remove element from tab index
43
+ el.tabIndex = -1;
44
+
45
+ // Backup original styles if any
46
+ const origStyle = el.getAttribute('style');
47
+
48
+ if (origStyle) {
49
+ el.setAttribute('data-choice-orig-style', origStyle);
50
+ }
51
+
52
+ el.setAttribute('data-choice', 'active');
53
+ }
54
+
55
+ reveal(): void {
56
+ const el = this.element;
57
+ // Reinstate passed element
58
+ removeClassesFromElement(el, this.classNames.input);
59
+ el.hidden = false;
60
+ el.removeAttribute('tabindex');
61
+
62
+ // Recover original styles if any
63
+ const origStyle = el.getAttribute('data-choice-orig-style');
64
+
65
+ if (origStyle) {
66
+ el.removeAttribute('data-choice-orig-style');
67
+ el.setAttribute('style', origStyle);
68
+ } else {
69
+ el.removeAttribute('style');
70
+ }
71
+ el.removeAttribute('data-choice');
72
+ }
73
+
74
+ enable(): void {
75
+ this.element.removeAttribute('disabled');
76
+ this.element.disabled = false;
77
+ this.isDisabled = false;
78
+ }
79
+
80
+ disable(): void {
81
+ this.element.setAttribute('disabled', '');
82
+ this.element.disabled = true;
83
+ this.isDisabled = true;
84
+ }
85
+
86
+ triggerEvent<K extends EventTypes>(eventType: EventTypes, data?: EventMap[K]['detail']): void {
87
+ dispatchEvent(this.element, eventType, data || {});
88
+ }
89
+ }
@@ -0,0 +1,3 @@
1
+ import WrappedElement from './wrapped-element';
2
+
3
+ export default class WrappedInput extends WrappedElement<HTMLInputElement> {}
@@ -0,0 +1,115 @@
1
+ import { parseCustomProperties } from '../lib/utils';
2
+ import { ClassNames } from '../interfaces/class-names';
3
+ import WrappedElement from './wrapped-element';
4
+ import { GroupFull } from '../interfaces/group-full';
5
+ import { ChoiceFull } from '../interfaces/choice-full';
6
+ import { stringToHtmlClass } from '../lib/choice-input';
7
+ import { isHtmlOptgroup, isHtmlOption } from '../lib/html-guard-statements';
8
+
9
+ export default class WrappedSelect extends WrappedElement<HTMLSelectElement> {
10
+ classNames: ClassNames;
11
+
12
+ template: (data: object) => HTMLOptionElement;
13
+
14
+ extractPlaceholder: boolean;
15
+
16
+ constructor({
17
+ element,
18
+ classNames,
19
+ template,
20
+ extractPlaceholder,
21
+ }: {
22
+ element: HTMLSelectElement;
23
+ classNames: ClassNames;
24
+ template: (data: object) => HTMLOptionElement;
25
+ extractPlaceholder: boolean;
26
+ }) {
27
+ super({ element, classNames });
28
+ this.template = template;
29
+ this.extractPlaceholder = extractPlaceholder;
30
+ }
31
+
32
+ get placeholderOption(): HTMLOptionElement | null {
33
+ return (
34
+ this.element.querySelector('option[value=""]') ||
35
+ // Backward compatibility layer for the non-standard placeholder attribute supported in older versions.
36
+ this.element.querySelector('option[placeholder]')
37
+ );
38
+ }
39
+
40
+ addOptions(choices: ChoiceFull[]): void {
41
+ const fragment = document.createDocumentFragment();
42
+ choices.forEach((obj) => {
43
+ const choice = obj;
44
+ if (choice.element) {
45
+ return;
46
+ }
47
+
48
+ const option = this.template(choice);
49
+ fragment.appendChild(option);
50
+ choice.element = option;
51
+ });
52
+ this.element.appendChild(fragment);
53
+ }
54
+
55
+ optionsAsChoices(): (ChoiceFull | GroupFull)[] {
56
+ const choices: (ChoiceFull | GroupFull)[] = [];
57
+
58
+ this.element.querySelectorAll<HTMLElement>(':scope > option, :scope > optgroup').forEach((e) => {
59
+ if (isHtmlOption(e)) {
60
+ choices.push(this._optionToChoice(e));
61
+ } else if (isHtmlOptgroup(e)) {
62
+ choices.push(this._optgroupToChoice(e));
63
+ }
64
+ // todo: hr as empty optgroup, requires displaying empty opt-groups to be useful
65
+ });
66
+
67
+ return choices;
68
+ }
69
+
70
+ // eslint-disable-next-line class-methods-use-this
71
+ _optionToChoice(option: HTMLOptionElement): ChoiceFull {
72
+ // option.value returns the label if there is no value attribute, which can break legacy placeholder attribute support
73
+ if (!option.hasAttribute('value') && option.hasAttribute('placeholder')) {
74
+ option.setAttribute('value', '');
75
+ option.value = '';
76
+ }
77
+
78
+ return {
79
+ id: 0,
80
+ group: null,
81
+ score: 0,
82
+ rank: 0,
83
+ value: option.value,
84
+ // https://developer.mozilla.org/en-US/docs/Web/HTML/Element/option
85
+ // This attribute is text for the label indicating the meaning of the option. If the `label` attribute isn't defined, its value is that of the element text content (ie `innerText`).
86
+ label: option.label,
87
+ element: option,
88
+ active: true,
89
+ // this returns true if nothing is selected on initial load, which will break placeholder support
90
+ selected: this.extractPlaceholder ? option.selected : option.hasAttribute('selected'),
91
+ disabled: option.disabled,
92
+ highlighted: false,
93
+ placeholder: this.extractPlaceholder && (!option.value || option.hasAttribute('placeholder')),
94
+ labelClass:
95
+ typeof option.dataset.labelClass !== 'undefined' ? stringToHtmlClass(option.dataset.labelClass) : undefined,
96
+ labelDescription:
97
+ typeof option.dataset.labelDescription !== 'undefined' ? option.dataset.labelDescription : undefined,
98
+ customProperties: parseCustomProperties(option.dataset.customProperties),
99
+ };
100
+ }
101
+
102
+ _optgroupToChoice(optgroup: HTMLOptGroupElement): GroupFull {
103
+ const options = optgroup.querySelectorAll('option');
104
+ const choices = Array.from(options).map((option) => this._optionToChoice(option));
105
+
106
+ return {
107
+ id: 0,
108
+ label: optgroup.label || '',
109
+ element: optgroup,
110
+ active: !!choices.length,
111
+ disabled: optgroup.disabled,
112
+ choices,
113
+ };
114
+ }
115
+ }
@@ -0,0 +1 @@
1
+ export const SCROLLING_SPEED: number = 4 as const;