govuk_publishing_components 58.1.1 → 58.2.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.
- checksums.yaml +4 -4
- data/app/assets/images/select-with-search/cross-icon.svg +6 -0
- data/app/assets/javascripts/govuk_publishing_components/analytics-ga4/ga4-search-tracker.js +4 -0
- data/app/assets/javascripts/govuk_publishing_components/components/select-with-search.js +57 -0
- data/app/assets/stylesheets/govuk_publishing_components/_all_components.scss +1 -0
- data/app/assets/stylesheets/govuk_publishing_components/components/_select-with-search.scss +168 -0
- data/app/assets/stylesheets/govuk_publishing_components/components/_select.scss +6 -0
- data/app/views/govuk_publishing_components/components/_select.html.erb +22 -23
- data/app/views/govuk_publishing_components/components/_select_with_search.html.erb +14 -0
- data/app/views/govuk_publishing_components/components/docs/select.yml +11 -0
- data/app/views/govuk_publishing_components/components/docs/select_with_search.yml +196 -0
- data/lib/govuk_publishing_components/presenters/select_helper.rb +8 -5
- data/lib/govuk_publishing_components/presenters/select_with_search_helper.rb +92 -0
- data/lib/govuk_publishing_components/version.rb +1 -1
- data/lib/govuk_publishing_components.rb +1 -0
- data/node_modules/choices.js/LICENSE +21 -0
- data/node_modules/choices.js/README.md +1360 -0
- data/node_modules/choices.js/package.json +173 -0
- data/node_modules/choices.js/public/assets/scripts/choices.js +5230 -0
- data/node_modules/choices.js/public/assets/scripts/choices.min.js +2 -0
- data/node_modules/choices.js/public/assets/scripts/choices.mjs +5222 -0
- data/node_modules/choices.js/public/assets/scripts/choices.search-basic.js +4748 -0
- data/node_modules/choices.js/public/assets/scripts/choices.search-basic.min.js +2 -0
- data/node_modules/choices.js/public/assets/scripts/choices.search-basic.mjs +4740 -0
- data/node_modules/choices.js/public/assets/scripts/choices.search-kmp.js +3631 -0
- data/node_modules/choices.js/public/assets/scripts/choices.search-kmp.min.js +2 -0
- data/node_modules/choices.js/public/assets/scripts/choices.search-kmp.mjs +3623 -0
- data/node_modules/choices.js/public/assets/scripts/choices.search-prefix.js +3590 -0
- data/node_modules/choices.js/public/assets/scripts/choices.search-prefix.min.js +2 -0
- data/node_modules/choices.js/public/assets/scripts/choices.search-prefix.mjs +3582 -0
- data/node_modules/choices.js/public/assets/styles/base.css +180 -0
- data/node_modules/choices.js/public/assets/styles/base.css.map +1 -0
- data/node_modules/choices.js/public/assets/styles/base.min.css +1 -0
- data/node_modules/choices.js/public/assets/styles/choices.css +338 -0
- data/node_modules/choices.js/public/assets/styles/choices.css.map +1 -0
- data/node_modules/choices.js/public/assets/styles/choices.min.css +1 -0
- data/node_modules/choices.js/public/types/src/index.d.ts +6 -0
- data/node_modules/choices.js/public/types/src/scripts/actions/choices.d.ts +30 -0
- data/node_modules/choices.js/public/types/src/scripts/actions/groups.d.ts +8 -0
- data/node_modules/choices.js/public/types/src/scripts/actions/items.d.ts +17 -0
- data/node_modules/choices.js/public/types/src/scripts/choices.d.ts +210 -0
- data/node_modules/choices.js/public/types/src/scripts/components/container.d.ts +36 -0
- data/node_modules/choices.js/public/types/src/scripts/components/dropdown.d.ts +21 -0
- data/node_modules/choices.js/public/types/src/scripts/components/index.d.ts +7 -0
- data/node_modules/choices.js/public/types/src/scripts/components/input.d.ts +37 -0
- data/node_modules/choices.js/public/types/src/scripts/components/list.d.ts +14 -0
- data/node_modules/choices.js/public/types/src/scripts/components/wrapped-element.d.ts +21 -0
- data/node_modules/choices.js/public/types/src/scripts/components/wrapped-input.d.ts +3 -0
- data/node_modules/choices.js/public/types/src/scripts/components/wrapped-select.d.ts +20 -0
- data/node_modules/choices.js/public/types/src/scripts/constants.d.ts +1 -0
- data/node_modules/choices.js/public/types/src/scripts/defaults.d.ts +4 -0
- data/node_modules/choices.js/public/types/src/scripts/interfaces/action-type.d.ts +13 -0
- data/node_modules/choices.js/public/types/src/scripts/interfaces/build-flags.d.ts +11 -0
- data/node_modules/choices.js/public/types/src/scripts/interfaces/choice-full.d.ts +23 -0
- data/node_modules/choices.js/public/types/src/scripts/interfaces/class-names.d.ts +61 -0
- data/node_modules/choices.js/public/types/src/scripts/interfaces/event-choice.d.ts +7 -0
- data/node_modules/choices.js/public/types/src/scripts/interfaces/event-type.d.ts +14 -0
- data/node_modules/choices.js/public/types/src/scripts/interfaces/group-full.d.ts +10 -0
- data/node_modules/choices.js/public/types/src/scripts/interfaces/index.d.ts +14 -0
- data/node_modules/choices.js/public/types/src/scripts/interfaces/input-choice.d.ts +15 -0
- data/node_modules/choices.js/public/types/src/scripts/interfaces/input-group.d.ts +10 -0
- data/node_modules/choices.js/public/types/src/scripts/interfaces/item.d.ts +17 -0
- data/node_modules/choices.js/public/types/src/scripts/interfaces/keycode-map.d.ts +13 -0
- data/node_modules/choices.js/public/types/src/scripts/interfaces/options.d.ts +566 -0
- data/node_modules/choices.js/public/types/src/scripts/interfaces/passed-element-type.d.ts +7 -0
- data/node_modules/choices.js/public/types/src/scripts/interfaces/passed-element.d.ts +95 -0
- data/node_modules/choices.js/public/types/src/scripts/interfaces/position-options-type.d.ts +1 -0
- data/node_modules/choices.js/public/types/src/scripts/interfaces/search.d.ts +11 -0
- data/node_modules/choices.js/public/types/src/scripts/interfaces/state.d.ts +10 -0
- data/node_modules/choices.js/public/types/src/scripts/interfaces/store.d.ts +64 -0
- data/node_modules/choices.js/public/types/src/scripts/interfaces/string-pre-escaped.d.ts +3 -0
- data/node_modules/choices.js/public/types/src/scripts/interfaces/string-untrusted.d.ts +4 -0
- data/node_modules/choices.js/public/types/src/scripts/interfaces/templates.d.ts +29 -0
- data/node_modules/choices.js/public/types/src/scripts/interfaces/types.d.ts +18 -0
- data/node_modules/choices.js/public/types/src/scripts/lib/choice-input.d.ts +9 -0
- data/node_modules/choices.js/public/types/src/scripts/lib/html-guard-statements.d.ts +4 -0
- data/node_modules/choices.js/public/types/src/scripts/lib/utils.d.ts +31 -0
- data/node_modules/choices.js/public/types/src/scripts/reducers/choices.d.ts +8 -0
- data/node_modules/choices.js/public/types/src/scripts/reducers/groups.d.ts +8 -0
- data/node_modules/choices.js/public/types/src/scripts/reducers/items.d.ts +9 -0
- data/node_modules/choices.js/public/types/src/scripts/search/fuse.d.ts +14 -0
- data/node_modules/choices.js/public/types/src/scripts/search/index.d.ts +3 -0
- data/node_modules/choices.js/public/types/src/scripts/search/kmp.d.ts +11 -0
- data/node_modules/choices.js/public/types/src/scripts/search/prefix-filter.d.ts +11 -0
- data/node_modules/choices.js/public/types/src/scripts/store/store.d.ts +59 -0
- data/node_modules/choices.js/public/types/src/scripts/templates.d.ts +8 -0
- data/node_modules/choices.js/src/entry.js +3 -0
- data/node_modules/choices.js/src/icons/cross-inverse.svg +1 -0
- data/node_modules/choices.js/src/icons/cross.svg +1 -0
- data/node_modules/choices.js/src/index.ts +8 -0
- data/node_modules/choices.js/src/scripts/actions/choices.ts +59 -0
- data/node_modules/choices.js/src/scripts/actions/groups.ts +14 -0
- data/node_modules/choices.js/src/scripts/actions/items.ts +34 -0
- data/node_modules/choices.js/src/scripts/choices.ts +2364 -0
- data/node_modules/choices.js/src/scripts/components/container.ts +157 -0
- data/node_modules/choices.js/src/scripts/components/dropdown.ts +50 -0
- data/node_modules/choices.js/src/scripts/components/index.ts +8 -0
- data/node_modules/choices.js/src/scripts/components/input.ts +146 -0
- data/node_modules/choices.js/src/scripts/components/list.ts +89 -0
- data/node_modules/choices.js/src/scripts/components/wrapped-element.ts +89 -0
- data/node_modules/choices.js/src/scripts/components/wrapped-input.ts +3 -0
- data/node_modules/choices.js/src/scripts/components/wrapped-select.ts +115 -0
- data/node_modules/choices.js/src/scripts/constants.ts +1 -0
- data/node_modules/choices.js/src/scripts/defaults.ts +93 -0
- data/node_modules/choices.js/src/scripts/interfaces/action-type.ts +15 -0
- data/node_modules/choices.js/src/scripts/interfaces/build-flags.ts +17 -0
- data/node_modules/choices.js/src/scripts/interfaces/choice-full.ts +30 -0
- data/node_modules/choices.js/src/scripts/interfaces/class-names.ts +61 -0
- data/node_modules/choices.js/src/scripts/interfaces/event-choice.ts +9 -0
- data/node_modules/choices.js/src/scripts/interfaces/event-type.ts +16 -0
- data/node_modules/choices.js/src/scripts/interfaces/group-full.ts +12 -0
- data/node_modules/choices.js/src/scripts/interfaces/index.ts +14 -0
- data/node_modules/choices.js/src/scripts/interfaces/input-choice.ts +17 -0
- data/node_modules/choices.js/src/scripts/interfaces/input-group.ts +11 -0
- data/node_modules/choices.js/src/scripts/interfaces/item.ts +17 -0
- data/node_modules/choices.js/src/scripts/interfaces/keycode-map.ts +13 -0
- data/node_modules/choices.js/src/scripts/interfaces/options.ts +619 -0
- data/node_modules/choices.js/src/scripts/interfaces/passed-element-type.ts +9 -0
- data/node_modules/choices.js/src/scripts/interfaces/passed-element.ts +96 -0
- data/node_modules/choices.js/src/scripts/interfaces/position-options-type.ts +1 -0
- data/node_modules/choices.js/src/scripts/interfaces/search.ts +12 -0
- data/node_modules/choices.js/src/scripts/interfaces/state.ts +12 -0
- data/node_modules/choices.js/src/scripts/interfaces/store.ts +84 -0
- data/node_modules/choices.js/src/scripts/interfaces/string-pre-escaped.ts +3 -0
- data/node_modules/choices.js/src/scripts/interfaces/string-untrusted.ts +5 -0
- data/node_modules/choices.js/src/scripts/interfaces/templates.ts +66 -0
- data/node_modules/choices.js/src/scripts/interfaces/types.ts +21 -0
- data/node_modules/choices.js/src/scripts/lib/choice-input.ts +88 -0
- data/node_modules/choices.js/src/scripts/lib/html-guard-statements.ts +7 -0
- data/node_modules/choices.js/src/scripts/lib/utils.ts +230 -0
- data/node_modules/choices.js/src/scripts/reducers/choices.ts +86 -0
- data/node_modules/choices.js/src/scripts/reducers/groups.ts +32 -0
- data/node_modules/choices.js/src/scripts/reducers/items.ts +86 -0
- data/node_modules/choices.js/src/scripts/search/fuse.ts +59 -0
- data/node_modules/choices.js/src/scripts/search/index.ts +17 -0
- data/node_modules/choices.js/src/scripts/search/kmp.ts +87 -0
- data/node_modules/choices.js/src/scripts/search/prefix-filter.ts +42 -0
- data/node_modules/choices.js/src/scripts/store/store.ts +184 -0
- data/node_modules/choices.js/src/scripts/templates.ts +409 -0
- data/node_modules/choices.js/src/styles/base.scss +189 -0
- data/node_modules/choices.js/src/styles/choices.scss +414 -0
- data/node_modules/choices.js/src/tsconfig.json +22 -0
- metadata +134 -1
@@ -0,0 +1,96 @@
|
|
1
|
+
import { InputChoice } from './input-choice';
|
2
|
+
import { EventChoice } from './event-choice';
|
3
|
+
|
4
|
+
/**
|
5
|
+
* Events fired by Choices behave the same as standard events. Each event is triggered on the element passed to Choices (accessible via `this.passedElement`. Arguments are accessible within the `event.detail` object.
|
6
|
+
*/
|
7
|
+
export interface EventMap {
|
8
|
+
/**
|
9
|
+
* Triggered each time an item is added (programmatically or by the user).
|
10
|
+
*
|
11
|
+
* **Input types affected:** text, select-one, select-multiple
|
12
|
+
*
|
13
|
+
* Arguments: id, value, label, groupValue
|
14
|
+
*/
|
15
|
+
addItem: CustomEvent<EventChoice>;
|
16
|
+
|
17
|
+
/**
|
18
|
+
* Triggered each time an item is removed (programmatically or by the user).
|
19
|
+
*
|
20
|
+
* **Input types affected:** text, select-one, select-multiple
|
21
|
+
*
|
22
|
+
* Arguments: id, value, label, groupValue
|
23
|
+
*/
|
24
|
+
removeItem: CustomEvent<EventChoice | undefined>;
|
25
|
+
|
26
|
+
/**
|
27
|
+
* Triggered each time an item is highlighted.
|
28
|
+
*
|
29
|
+
* **Input types affected:** text, select-multiple
|
30
|
+
*
|
31
|
+
* Arguments: id, value, label, groupValue
|
32
|
+
*/
|
33
|
+
highlightItem: CustomEvent<EventChoice | undefined>;
|
34
|
+
|
35
|
+
/**
|
36
|
+
* Triggered each time an item is unhighlighted.
|
37
|
+
*
|
38
|
+
* **Input types affected:** text, select-multiple
|
39
|
+
*
|
40
|
+
* Arguments: id, value, label, groupValue
|
41
|
+
*/
|
42
|
+
unhighlightItem: CustomEvent<EventChoice | undefined>;
|
43
|
+
|
44
|
+
/**
|
45
|
+
* Triggered each time a choice is selected **by a user**, regardless if it changes the value of the input.
|
46
|
+
*
|
47
|
+
* **Input types affected:** select-one, select-multiple
|
48
|
+
*
|
49
|
+
* Arguments: choice: Choice
|
50
|
+
*/
|
51
|
+
choice: CustomEvent<{ choice: InputChoice }>;
|
52
|
+
|
53
|
+
/**
|
54
|
+
* Triggered each time an item is added/removed **by a user**.
|
55
|
+
*
|
56
|
+
* **Input types affected:** text, select-one, select-multiple
|
57
|
+
*
|
58
|
+
* Arguments: value
|
59
|
+
*/
|
60
|
+
change: CustomEvent<{ value: string }>;
|
61
|
+
|
62
|
+
/**
|
63
|
+
* Triggered when a user types into an input to search choices. When a search is ended, a search event with an empty value with no resultCount is triggered.
|
64
|
+
*
|
65
|
+
* **Input types affected:** select-one, select-multiple
|
66
|
+
*
|
67
|
+
* Arguments: value, resultCount
|
68
|
+
*/
|
69
|
+
search: CustomEvent<{ value: string; resultCount: number }>;
|
70
|
+
|
71
|
+
/**
|
72
|
+
* Triggered when the dropdown is shown.
|
73
|
+
*
|
74
|
+
* **Input types affected:** select-one, select-multiple
|
75
|
+
*
|
76
|
+
* Arguments: -
|
77
|
+
*/
|
78
|
+
showDropdown: CustomEvent<undefined>;
|
79
|
+
|
80
|
+
/**
|
81
|
+
* Triggered when the dropdown is hidden.
|
82
|
+
*
|
83
|
+
* **Input types affected:** select-one, select-multiple
|
84
|
+
*
|
85
|
+
* Arguments: -
|
86
|
+
*/
|
87
|
+
hideDropdown: CustomEvent<undefined>;
|
88
|
+
|
89
|
+
/**
|
90
|
+
* Triggered when a choice from the dropdown is highlighted.
|
91
|
+
*
|
92
|
+
* Input types affected: select-one, select-multiple
|
93
|
+
* Arguments: el is the choice.passedElement that was affected.
|
94
|
+
*/
|
95
|
+
highlightChoice: CustomEvent<{ el: HTMLElement }>;
|
96
|
+
}
|
@@ -0,0 +1 @@
|
|
1
|
+
export type PositionOptionsType = 'auto' | 'top' | 'bottom';
|
@@ -0,0 +1,12 @@
|
|
1
|
+
export interface SearchResult<T extends object> {
|
2
|
+
item: T;
|
3
|
+
score: number;
|
4
|
+
rank: number; // values of 0 means this item is not in the search-result set, and should be discarded
|
5
|
+
}
|
6
|
+
|
7
|
+
export interface Searcher<T extends object> {
|
8
|
+
reset(): void;
|
9
|
+
isEmptyIndex(): boolean;
|
10
|
+
index(data: T[]): void;
|
11
|
+
search(needle: string): SearchResult<T>[];
|
12
|
+
}
|
@@ -0,0 +1,12 @@
|
|
1
|
+
import { ChoiceFull } from './choice-full';
|
2
|
+
import { GroupFull } from './group-full';
|
3
|
+
|
4
|
+
export interface State {
|
5
|
+
choices: ChoiceFull[];
|
6
|
+
groups: GroupFull[];
|
7
|
+
items: ChoiceFull[];
|
8
|
+
}
|
9
|
+
|
10
|
+
export type StateChangeSet = {
|
11
|
+
[K in keyof State]: boolean;
|
12
|
+
};
|
@@ -0,0 +1,84 @@
|
|
1
|
+
import { StateChangeSet, State } from './state';
|
2
|
+
import { ChoiceFull } from './choice-full';
|
3
|
+
import { GroupFull } from './group-full';
|
4
|
+
import { ActionTypes } from './action-type';
|
5
|
+
|
6
|
+
export interface AnyAction<A extends ActionTypes = ActionTypes> {
|
7
|
+
type: A;
|
8
|
+
}
|
9
|
+
|
10
|
+
export interface StateUpdate<T> {
|
11
|
+
update: boolean;
|
12
|
+
state: T;
|
13
|
+
}
|
14
|
+
|
15
|
+
export type Reducer<T> = (state: T, action: AnyAction, context?: unknown) => StateUpdate<T>;
|
16
|
+
|
17
|
+
export type StoreListener = (changes: StateChangeSet) => void;
|
18
|
+
|
19
|
+
export interface Store {
|
20
|
+
dispatch(action: AnyAction): void;
|
21
|
+
|
22
|
+
subscribe(onChange: StoreListener): void;
|
23
|
+
|
24
|
+
withTxn(func: () => void): void;
|
25
|
+
|
26
|
+
reset(): void;
|
27
|
+
|
28
|
+
get defaultState(): State;
|
29
|
+
|
30
|
+
/**
|
31
|
+
* Get store object
|
32
|
+
*/
|
33
|
+
get state(): State;
|
34
|
+
|
35
|
+
/**
|
36
|
+
* Get items from store
|
37
|
+
*/
|
38
|
+
get items(): ChoiceFull[];
|
39
|
+
|
40
|
+
/**
|
41
|
+
* Get highlighted items from store
|
42
|
+
*/
|
43
|
+
get highlightedActiveItems(): ChoiceFull[];
|
44
|
+
|
45
|
+
/**
|
46
|
+
* Get choices from store
|
47
|
+
*/
|
48
|
+
get choices(): ChoiceFull[];
|
49
|
+
|
50
|
+
/**
|
51
|
+
* Get active choices from store
|
52
|
+
*/
|
53
|
+
get activeChoices(): ChoiceFull[];
|
54
|
+
|
55
|
+
/**
|
56
|
+
* Get choices that can be searched (excluding placeholders)
|
57
|
+
*/
|
58
|
+
get searchableChoices(): ChoiceFull[];
|
59
|
+
|
60
|
+
/**
|
61
|
+
* Get groups from store
|
62
|
+
*/
|
63
|
+
get groups(): GroupFull[];
|
64
|
+
|
65
|
+
/**
|
66
|
+
* Get active groups from store
|
67
|
+
*/
|
68
|
+
get activeGroups(): GroupFull[];
|
69
|
+
|
70
|
+
/**
|
71
|
+
* Get loading state from store
|
72
|
+
*/
|
73
|
+
inTxn(): boolean;
|
74
|
+
|
75
|
+
/**
|
76
|
+
* Get single choice by it's ID
|
77
|
+
*/
|
78
|
+
getChoiceById(id: number): ChoiceFull | undefined;
|
79
|
+
|
80
|
+
/**
|
81
|
+
* Get group by group id
|
82
|
+
*/
|
83
|
+
getGroupById(id: number): GroupFull | undefined;
|
84
|
+
}
|
@@ -0,0 +1,66 @@
|
|
1
|
+
import { PassedElementType } from './passed-element-type';
|
2
|
+
import { StringPreEscaped } from './string-pre-escaped';
|
3
|
+
import { ChoiceFull } from './choice-full';
|
4
|
+
import { GroupFull } from './group-full';
|
5
|
+
// eslint-disable-next-line import/no-cycle
|
6
|
+
import { Options } from './options';
|
7
|
+
import { Types } from './types';
|
8
|
+
|
9
|
+
export type TemplateOptions = Pick<
|
10
|
+
Options,
|
11
|
+
| 'classNames'
|
12
|
+
| 'allowHTML'
|
13
|
+
| 'removeItemButtonAlignLeft'
|
14
|
+
| 'removeItemIconText'
|
15
|
+
| 'removeItemLabelText'
|
16
|
+
| 'searchEnabled'
|
17
|
+
| 'labelId'
|
18
|
+
>;
|
19
|
+
|
20
|
+
export const NoticeTypes = {
|
21
|
+
noChoices: 'no-choices',
|
22
|
+
noResults: 'no-results',
|
23
|
+
addChoice: 'add-choice',
|
24
|
+
generic: '',
|
25
|
+
} as const;
|
26
|
+
export type NoticeType = Types.ValueOf<typeof NoticeTypes>;
|
27
|
+
|
28
|
+
export type CallbackOnCreateTemplatesFn = (
|
29
|
+
template: Types.StrToEl,
|
30
|
+
escapeForTemplate: Types.EscapeForTemplateFn,
|
31
|
+
getClassNames: Types.GetClassNamesFn,
|
32
|
+
) => Partial<Templates>;
|
33
|
+
|
34
|
+
export interface Templates {
|
35
|
+
containerOuter(
|
36
|
+
options: TemplateOptions,
|
37
|
+
dir: HTMLElement['dir'],
|
38
|
+
isSelectElement: boolean,
|
39
|
+
isSelectOneElement: boolean,
|
40
|
+
searchEnabled: boolean,
|
41
|
+
passedElementType: PassedElementType,
|
42
|
+
labelId: string,
|
43
|
+
): HTMLDivElement;
|
44
|
+
|
45
|
+
containerInner({ classNames: { containerInner } }: TemplateOptions): HTMLDivElement;
|
46
|
+
|
47
|
+
itemList(options: TemplateOptions, isSelectOneElement: boolean): HTMLDivElement;
|
48
|
+
|
49
|
+
placeholder(options: TemplateOptions, value: StringPreEscaped | string): HTMLDivElement;
|
50
|
+
|
51
|
+
item(options: TemplateOptions, choice: ChoiceFull, removeItemButton: boolean): HTMLDivElement;
|
52
|
+
|
53
|
+
choiceList(options: TemplateOptions, isSelectOneElement: boolean): HTMLDivElement;
|
54
|
+
|
55
|
+
choiceGroup(options: TemplateOptions, group: GroupFull): HTMLDivElement;
|
56
|
+
|
57
|
+
choice(options: TemplateOptions, choice: ChoiceFull, selectText: string, groupText?: string): HTMLDivElement;
|
58
|
+
|
59
|
+
input(options: TemplateOptions, placeholderValue: string | null): HTMLInputElement;
|
60
|
+
|
61
|
+
dropdown(options: TemplateOptions): HTMLDivElement;
|
62
|
+
|
63
|
+
notice(options: TemplateOptions, innerText: string, type: NoticeType): HTMLDivElement;
|
64
|
+
|
65
|
+
option(choice: ChoiceFull): HTMLOptionElement;
|
66
|
+
}
|
@@ -0,0 +1,21 @@
|
|
1
|
+
import { StringUntrusted } from './string-untrusted';
|
2
|
+
import { StringPreEscaped } from './string-pre-escaped';
|
3
|
+
|
4
|
+
export namespace Types {
|
5
|
+
export type StrToEl = (str: string) => HTMLElement | HTMLInputElement | HTMLOptionElement;
|
6
|
+
export type EscapeForTemplateFn = (allowHTML: boolean, s: StringUntrusted | StringPreEscaped | string) => string;
|
7
|
+
export type GetClassNamesFn = (s: string | Array<string>) => string;
|
8
|
+
export type StringFunction = () => string;
|
9
|
+
export type NoticeStringFunction = (value: string, valueRaw: string) => string;
|
10
|
+
export type NoticeLimitFunction = (maxItemCount: number) => string;
|
11
|
+
export type FilterFunction = (value: string) => boolean;
|
12
|
+
export type ValueCompareFunction = (value1: string, value2: string) => boolean;
|
13
|
+
|
14
|
+
export interface RecordToCompare {
|
15
|
+
value?: StringUntrusted | string;
|
16
|
+
label?: StringUntrusted | string;
|
17
|
+
}
|
18
|
+
export type ValueOf<T extends object> = T[keyof T];
|
19
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
20
|
+
export type CustomProperties = Record<string, any> | string;
|
21
|
+
}
|
@@ -0,0 +1,88 @@
|
|
1
|
+
import { InputChoice } from '../interfaces/input-choice';
|
2
|
+
import { InputGroup } from '../interfaces/input-group';
|
3
|
+
import { GroupFull } from '../interfaces/group-full';
|
4
|
+
import { ChoiceFull } from '../interfaces/choice-full';
|
5
|
+
import { sanitise, unwrapStringForRaw } from './utils';
|
6
|
+
|
7
|
+
type MappedInputTypeToChoiceType<T extends string | InputChoice | InputGroup> = T extends InputGroup
|
8
|
+
? GroupFull
|
9
|
+
: ChoiceFull;
|
10
|
+
|
11
|
+
export const coerceBool = (arg: unknown, defaultValue: boolean = true): boolean =>
|
12
|
+
typeof arg === 'undefined' ? defaultValue : !!arg;
|
13
|
+
|
14
|
+
export const stringToHtmlClass = (input: string | string[] | undefined): string[] | undefined => {
|
15
|
+
if (typeof input === 'string') {
|
16
|
+
// eslint-disable-next-line no-param-reassign
|
17
|
+
input = input.split(' ').filter((s) => s.length);
|
18
|
+
}
|
19
|
+
|
20
|
+
if (Array.isArray(input) && input.length) {
|
21
|
+
return input;
|
22
|
+
}
|
23
|
+
|
24
|
+
return undefined;
|
25
|
+
};
|
26
|
+
|
27
|
+
export const mapInputToChoice = <T extends string | InputChoice | InputGroup>(
|
28
|
+
value: T,
|
29
|
+
allowGroup: boolean,
|
30
|
+
allowRawString: boolean = true,
|
31
|
+
): MappedInputTypeToChoiceType<T> => {
|
32
|
+
if (typeof value === 'string') {
|
33
|
+
const sanitisedValue = sanitise(value);
|
34
|
+
const userValue = allowRawString || sanitisedValue === value ? value : { escaped: sanitisedValue, raw: value };
|
35
|
+
|
36
|
+
const result: ChoiceFull = mapInputToChoice<InputChoice>(
|
37
|
+
{
|
38
|
+
value,
|
39
|
+
label: userValue,
|
40
|
+
selected: true,
|
41
|
+
},
|
42
|
+
false,
|
43
|
+
);
|
44
|
+
|
45
|
+
return result as MappedInputTypeToChoiceType<T>;
|
46
|
+
}
|
47
|
+
|
48
|
+
const groupOrChoice = value as InputChoice | InputGroup;
|
49
|
+
if ('choices' in groupOrChoice) {
|
50
|
+
if (!allowGroup) {
|
51
|
+
// https://developer.mozilla.org/en-US/docs/Web/HTML/Element/optgroup
|
52
|
+
throw new TypeError(`optGroup is not allowed`);
|
53
|
+
}
|
54
|
+
const group = groupOrChoice;
|
55
|
+
const choices = group.choices.map((e) => mapInputToChoice<InputChoice>(e, false));
|
56
|
+
|
57
|
+
const result: GroupFull = {
|
58
|
+
id: 0, // actual ID will be assigned during _addGroup
|
59
|
+
label: unwrapStringForRaw(group.label) || group.value,
|
60
|
+
active: !!choices.length,
|
61
|
+
disabled: !!group.disabled,
|
62
|
+
choices,
|
63
|
+
};
|
64
|
+
|
65
|
+
return result as MappedInputTypeToChoiceType<T>;
|
66
|
+
}
|
67
|
+
|
68
|
+
const choice = groupOrChoice;
|
69
|
+
|
70
|
+
const result: ChoiceFull = {
|
71
|
+
id: 0, // actual ID will be assigned during _addChoice
|
72
|
+
group: null, // actual group will be assigned during _addGroup but before _addChoice
|
73
|
+
score: 0, // used in search
|
74
|
+
rank: 0, // used in search, stable sort order
|
75
|
+
value: choice.value,
|
76
|
+
label: choice.label || choice.value,
|
77
|
+
active: coerceBool(choice.active),
|
78
|
+
selected: coerceBool(choice.selected, false),
|
79
|
+
disabled: coerceBool(choice.disabled, false),
|
80
|
+
placeholder: coerceBool(choice.placeholder, false),
|
81
|
+
highlighted: false,
|
82
|
+
labelClass: stringToHtmlClass(choice.labelClass),
|
83
|
+
labelDescription: choice.labelDescription,
|
84
|
+
customProperties: choice.customProperties,
|
85
|
+
};
|
86
|
+
|
87
|
+
return result as MappedInputTypeToChoiceType<T>;
|
88
|
+
};
|
@@ -0,0 +1,7 @@
|
|
1
|
+
export const isHtmlInputElement = (e: Element): e is HTMLInputElement => e.tagName === 'INPUT';
|
2
|
+
|
3
|
+
export const isHtmlSelectElement = (e: Element): e is HTMLSelectElement => e.tagName === 'SELECT';
|
4
|
+
|
5
|
+
export const isHtmlOption = (e: Element): e is HTMLOptionElement => e.tagName === 'OPTION';
|
6
|
+
|
7
|
+
export const isHtmlOptgroup = (e: Element): e is HTMLOptGroupElement => e.tagName === 'OPTGROUP';
|
@@ -0,0 +1,230 @@
|
|
1
|
+
import { EventTypes } from '../interfaces/event-type';
|
2
|
+
import { StringUntrusted } from '../interfaces/string-untrusted';
|
3
|
+
import { StringPreEscaped } from '../interfaces/string-pre-escaped';
|
4
|
+
import { ChoiceFull } from '../interfaces/choice-full';
|
5
|
+
import { Types } from '../interfaces/types';
|
6
|
+
import { canUseDom } from '../interfaces/build-flags';
|
7
|
+
|
8
|
+
const getRandomNumber = (min: number, max: number): number => Math.floor(Math.random() * (max - min) + min);
|
9
|
+
|
10
|
+
const generateChars = (length: number): string =>
|
11
|
+
Array.from({ length }, () => getRandomNumber(0, 36).toString(36)).join('');
|
12
|
+
|
13
|
+
export const generateId = (element: HTMLInputElement | HTMLSelectElement, prefix: string): string => {
|
14
|
+
let id = element.id || (element.name && `${element.name}-${generateChars(2)}`) || generateChars(4);
|
15
|
+
id = id.replace(/(:|\.|\[|\]|,)/g, '');
|
16
|
+
id = `${prefix}-${id}`;
|
17
|
+
|
18
|
+
return id;
|
19
|
+
};
|
20
|
+
|
21
|
+
export const getAdjacentEl = (startEl: HTMLElement, selector: string, direction = 1): HTMLElement | null => {
|
22
|
+
const prop = `${direction > 0 ? 'next' : 'previous'}ElementSibling`;
|
23
|
+
|
24
|
+
let sibling = startEl[prop];
|
25
|
+
while (sibling) {
|
26
|
+
if (sibling.matches(selector)) {
|
27
|
+
return sibling;
|
28
|
+
}
|
29
|
+
sibling = sibling[prop];
|
30
|
+
}
|
31
|
+
|
32
|
+
return null;
|
33
|
+
};
|
34
|
+
|
35
|
+
export const isScrolledIntoView = (element: HTMLElement, parent: HTMLElement, direction = 1): boolean => {
|
36
|
+
let isVisible: boolean;
|
37
|
+
|
38
|
+
if (direction > 0) {
|
39
|
+
// In view from bottom
|
40
|
+
isVisible = parent.scrollTop + parent.offsetHeight >= element.offsetTop + element.offsetHeight;
|
41
|
+
} else {
|
42
|
+
// In view from top
|
43
|
+
isVisible = element.offsetTop >= parent.scrollTop;
|
44
|
+
}
|
45
|
+
|
46
|
+
return isVisible;
|
47
|
+
};
|
48
|
+
|
49
|
+
export const sanitise = <T>(value: T | StringUntrusted | StringPreEscaped | string): T | string => {
|
50
|
+
if (typeof value !== 'string') {
|
51
|
+
if (value === null || value === undefined) {
|
52
|
+
return '';
|
53
|
+
}
|
54
|
+
|
55
|
+
if (typeof value === 'object') {
|
56
|
+
if ('raw' in value) {
|
57
|
+
return sanitise(value.raw);
|
58
|
+
}
|
59
|
+
if ('trusted' in value) {
|
60
|
+
return value.trusted;
|
61
|
+
}
|
62
|
+
}
|
63
|
+
|
64
|
+
return value;
|
65
|
+
}
|
66
|
+
|
67
|
+
return value
|
68
|
+
.replace(/&/g, '&')
|
69
|
+
.replace(/>/g, '>')
|
70
|
+
.replace(/</g, '<')
|
71
|
+
.replace(/'/g, ''')
|
72
|
+
.replace(/"/g, '"');
|
73
|
+
};
|
74
|
+
|
75
|
+
export const strToEl = ((): ((str: string) => Element) => {
|
76
|
+
if (!canUseDom) {
|
77
|
+
// @ts-expect-error Do not run strToEl in non-browser environment
|
78
|
+
return (): void => {};
|
79
|
+
}
|
80
|
+
const tmpEl = document.createElement('div');
|
81
|
+
|
82
|
+
return (str): Element => {
|
83
|
+
tmpEl.innerHTML = str.trim();
|
84
|
+
const firstChild = tmpEl.children[0];
|
85
|
+
|
86
|
+
while (tmpEl.firstChild) {
|
87
|
+
tmpEl.removeChild(tmpEl.firstChild);
|
88
|
+
}
|
89
|
+
|
90
|
+
return firstChild;
|
91
|
+
};
|
92
|
+
})();
|
93
|
+
|
94
|
+
export const resolveNoticeFunction = (fn: Types.NoticeStringFunction | string, value: string): string => {
|
95
|
+
return typeof fn === 'function' ? fn(sanitise(value), value) : fn;
|
96
|
+
};
|
97
|
+
|
98
|
+
export const resolveStringFunction = (fn: Types.StringFunction | string): string => {
|
99
|
+
return typeof fn === 'function' ? fn() : fn;
|
100
|
+
};
|
101
|
+
|
102
|
+
export const unwrapStringForRaw = (s?: StringUntrusted | StringPreEscaped | string): string => {
|
103
|
+
if (typeof s === 'string') {
|
104
|
+
return s;
|
105
|
+
}
|
106
|
+
|
107
|
+
if (typeof s === 'object') {
|
108
|
+
if ('trusted' in s) {
|
109
|
+
return s.trusted;
|
110
|
+
}
|
111
|
+
if ('raw' in s) {
|
112
|
+
return s.raw;
|
113
|
+
}
|
114
|
+
}
|
115
|
+
|
116
|
+
return '';
|
117
|
+
};
|
118
|
+
|
119
|
+
export const unwrapStringForEscaped = (s?: StringUntrusted | StringPreEscaped | string): string => {
|
120
|
+
if (typeof s === 'string') {
|
121
|
+
return s;
|
122
|
+
}
|
123
|
+
|
124
|
+
if (typeof s === 'object') {
|
125
|
+
if ('escaped' in s) {
|
126
|
+
return s.escaped;
|
127
|
+
}
|
128
|
+
if ('trusted' in s) {
|
129
|
+
return s.trusted;
|
130
|
+
}
|
131
|
+
}
|
132
|
+
|
133
|
+
return '';
|
134
|
+
};
|
135
|
+
|
136
|
+
export const escapeForTemplate = (allowHTML: boolean, s: StringUntrusted | StringPreEscaped | string): string =>
|
137
|
+
allowHTML ? unwrapStringForEscaped(s) : (sanitise(s) as string);
|
138
|
+
|
139
|
+
export const setElementHtml = (
|
140
|
+
el: HTMLElement,
|
141
|
+
allowHtml: boolean,
|
142
|
+
html: StringUntrusted | StringPreEscaped | string,
|
143
|
+
): void => {
|
144
|
+
el.innerHTML = escapeForTemplate(allowHtml, html);
|
145
|
+
};
|
146
|
+
|
147
|
+
export const sortByAlpha = (
|
148
|
+
{ value, label = value }: Types.RecordToCompare,
|
149
|
+
{ value: value2, label: label2 = value2 }: Types.RecordToCompare,
|
150
|
+
): number =>
|
151
|
+
unwrapStringForRaw(label).localeCompare(unwrapStringForRaw(label2), [], {
|
152
|
+
sensitivity: 'base',
|
153
|
+
ignorePunctuation: true,
|
154
|
+
numeric: true,
|
155
|
+
});
|
156
|
+
|
157
|
+
export const sortByScore = (a: Pick<ChoiceFull, 'score'>, b: Pick<ChoiceFull, 'score'>): number => {
|
158
|
+
return a.score - b.score;
|
159
|
+
};
|
160
|
+
|
161
|
+
export const sortByRank = (a: Pick<ChoiceFull, 'rank'>, b: Pick<ChoiceFull, 'rank'>): number => {
|
162
|
+
return a.rank - b.rank;
|
163
|
+
};
|
164
|
+
|
165
|
+
export const dispatchEvent = (element: HTMLElement, type: EventTypes, customArgs: object | null = null): boolean => {
|
166
|
+
const event = new CustomEvent(type, {
|
167
|
+
detail: customArgs,
|
168
|
+
bubbles: true,
|
169
|
+
cancelable: true,
|
170
|
+
});
|
171
|
+
|
172
|
+
return element.dispatchEvent(event);
|
173
|
+
};
|
174
|
+
|
175
|
+
export const cloneObject = <T>(obj: T): T => (obj !== undefined ? JSON.parse(JSON.stringify(obj)) : undefined);
|
176
|
+
|
177
|
+
/**
|
178
|
+
* Returns an array of keys present on the first but missing on the second object
|
179
|
+
*/
|
180
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
181
|
+
export const diff = (a: Record<string, any>, b: Record<string, any>): string[] => {
|
182
|
+
const aKeys = Object.keys(a).sort();
|
183
|
+
const bKeys = Object.keys(b).sort();
|
184
|
+
|
185
|
+
return aKeys.filter((i) => bKeys.indexOf(i) < 0);
|
186
|
+
};
|
187
|
+
|
188
|
+
export const getClassNames = (ClassNames: Array<string> | string): Array<string> => {
|
189
|
+
return Array.isArray(ClassNames) ? ClassNames : [ClassNames];
|
190
|
+
};
|
191
|
+
|
192
|
+
export const getClassNamesSelector = (option: string | Array<string> | null): string => {
|
193
|
+
if (option && Array.isArray(option)) {
|
194
|
+
return option
|
195
|
+
.map((item) => {
|
196
|
+
return `.${item}`;
|
197
|
+
})
|
198
|
+
.join('');
|
199
|
+
}
|
200
|
+
|
201
|
+
return `.${option}`;
|
202
|
+
};
|
203
|
+
|
204
|
+
export const addClassesToElement = (element: HTMLElement, className: Array<string> | string): void => {
|
205
|
+
element.classList.add(...getClassNames(className));
|
206
|
+
};
|
207
|
+
|
208
|
+
export const removeClassesFromElement = (element: HTMLElement, className: Array<string> | string): void => {
|
209
|
+
element.classList.remove(...getClassNames(className));
|
210
|
+
};
|
211
|
+
|
212
|
+
export const parseCustomProperties = (customProperties?: string): object | string => {
|
213
|
+
if (typeof customProperties !== 'undefined') {
|
214
|
+
try {
|
215
|
+
return JSON.parse(customProperties);
|
216
|
+
} catch (e) {
|
217
|
+
return customProperties;
|
218
|
+
}
|
219
|
+
}
|
220
|
+
|
221
|
+
return {};
|
222
|
+
};
|
223
|
+
|
224
|
+
export const updateClassList = (item: ChoiceFull, add: string | string[], remove: string | string[]): void => {
|
225
|
+
const { itemEl } = item;
|
226
|
+
if (itemEl) {
|
227
|
+
removeClassesFromElement(itemEl, remove);
|
228
|
+
addClassesToElement(itemEl, add);
|
229
|
+
}
|
230
|
+
};
|