@peckadesign/pd-naja 1.6.0 → 2.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.
- package/README.md +16 -0
- package/dist/PdNaja.esm.js +229 -54
- package/dist/PdNaja.esm.js.map +1 -1
- package/dist/{utils → classes}/ControlManager.d.ts +5 -3
- package/dist/classes/Suggest.d.ts +34 -0
- package/dist/extensions/BtnSpinnerExtension.d.ts +5 -9
- package/dist/extensions/SpinnerExtension.d.ts +5 -7
- package/dist/extensions/SuggestExtension.d.ts +18 -0
- package/dist/index.esm.d.ts +4 -3
- package/dist/index.esm.js +229 -54
- package/dist/index.esm.js.map +1 -1
- package/dist/types.d.ts +10 -0
- package/dist/utils.d.ts +3 -0
- package/package.json +2 -4
- package/src/{utils → classes}/ControlManager.ts +10 -8
- package/src/classes/Suggest.ts +215 -0
- package/src/extensions/BtnSpinnerExtension.ts +10 -32
- package/src/extensions/SpinnerExtension.ts +9 -24
- package/src/extensions/SuggestExtension.ts +66 -0
- package/src/extensions/ViewTransitionExtension.txt +32 -0
- package/src/index.esm.ts +4 -4
- package/src/{utils/Control.ts → types.ts} +9 -2
- package/src/utils.ts +22 -0
- package/dist/utils/Control.d.ts +0 -4
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
import { SpinnerPropsFn, SpinnerType, WithSpinner } from '../types'
|
|
2
|
+
import { hideSpinner, showSpinner } from '../utils'
|
|
3
|
+
|
|
4
|
+
type SuggestOptions = {
|
|
5
|
+
minLength: number
|
|
6
|
+
timeout: number
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export class Suggest {
|
|
10
|
+
public readonly spinner?: SpinnerType
|
|
11
|
+
public readonly getSpinnerProps?: SpinnerPropsFn
|
|
12
|
+
|
|
13
|
+
public static readonly className = 'js-suggest'
|
|
14
|
+
public readonly inputClassName = `${Suggest.className}__input`
|
|
15
|
+
public readonly buttonClassName = `${Suggest.className}__btn`
|
|
16
|
+
public readonly suggestClassName = `${Suggest.className}__suggest`
|
|
17
|
+
public readonly linkClassName = `${Suggest.className}__link`
|
|
18
|
+
|
|
19
|
+
private readonly form: HTMLFormElement
|
|
20
|
+
private readonly input: HTMLInputElement
|
|
21
|
+
private readonly button: HTMLButtonElement
|
|
22
|
+
private readonly suggest: HTMLElement
|
|
23
|
+
|
|
24
|
+
private suggestSpinner: Element | undefined
|
|
25
|
+
|
|
26
|
+
private isOpen: boolean = false
|
|
27
|
+
private timer: ReturnType<typeof setTimeout> | undefined = undefined
|
|
28
|
+
|
|
29
|
+
private lastSearched: string = ''
|
|
30
|
+
|
|
31
|
+
private readonly options: SuggestOptions = {
|
|
32
|
+
minLength: 2,
|
|
33
|
+
timeout: 200
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
public constructor(
|
|
37
|
+
form: HTMLFormElement,
|
|
38
|
+
options: Partial<SuggestOptions> = {},
|
|
39
|
+
spinner: SpinnerType | undefined = undefined,
|
|
40
|
+
getSpinnerProps: SpinnerPropsFn = undefined
|
|
41
|
+
) {
|
|
42
|
+
this.form = form
|
|
43
|
+
this.spinner = spinner
|
|
44
|
+
this.getSpinnerProps = getSpinnerProps
|
|
45
|
+
|
|
46
|
+
const input = form.querySelector<HTMLInputElement>(`.${this.inputClassName}`)
|
|
47
|
+
const button = form.querySelector<HTMLButtonElement>(`.${this.buttonClassName}`)
|
|
48
|
+
const suggest = form.querySelector<HTMLElement>(`.${this.suggestClassName}`)
|
|
49
|
+
|
|
50
|
+
if (!input || !button || !suggest) {
|
|
51
|
+
throw new Error('Suggest: Missing input, button or suggest element.')
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
this.input = input
|
|
55
|
+
this.button = button
|
|
56
|
+
this.suggest = suggest
|
|
57
|
+
|
|
58
|
+
this.options = { ...this.options, ...options }
|
|
59
|
+
|
|
60
|
+
this.input.addEventListener('focus', this.showSuggest.bind(this))
|
|
61
|
+
this.input.addEventListener('blur', this.hideSuggest.bind(this))
|
|
62
|
+
|
|
63
|
+
this.input.addEventListener('keydown', this.handleInputKeydown.bind(this))
|
|
64
|
+
this.input.addEventListener('keyup', this.handleInputKeyup.bind(this))
|
|
65
|
+
|
|
66
|
+
this.suggest.addEventListener('mousedown', this.handleSuggestMousedown.bind(this))
|
|
67
|
+
|
|
68
|
+
this.form._suggest = this
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
private isEmpty(): boolean {
|
|
72
|
+
return this.suggest.childElementCount === 0
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
private showSuggest(): void {
|
|
76
|
+
if (this.isEmpty() || this.input.value.length < this.options.minLength) {
|
|
77
|
+
return
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const event = new CustomEvent('show.suggest', { bubbles: true, cancelable: true })
|
|
81
|
+
this.suggest.dispatchEvent(event)
|
|
82
|
+
|
|
83
|
+
if (event.defaultPrevented) {
|
|
84
|
+
return
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
this.isOpen = true
|
|
88
|
+
this.suggest.classList.add(`${this.suggestClassName}--shown`)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
private hideSuggest(): void {
|
|
92
|
+
const event = new CustomEvent('hide.suggest', { bubbles: true, cancelable: true })
|
|
93
|
+
this.suggest.dispatchEvent(event)
|
|
94
|
+
|
|
95
|
+
if (event.defaultPrevented) {
|
|
96
|
+
return
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
this.isOpen = false
|
|
100
|
+
this.suggest.classList.remove(`${this.suggestClassName}--shown`)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
private emptySuggest(): void {
|
|
104
|
+
this.suggest.classList.add(`${this.suggestClassName}--empty`)
|
|
105
|
+
this.suggest.replaceChildren()
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
public startSuggest(): void {
|
|
109
|
+
this.suggest.dispatchEvent(new CustomEvent('loading.suggest', { bubbles: true }))
|
|
110
|
+
this.input.classList.add(`${this.inputClassName}--loading`)
|
|
111
|
+
|
|
112
|
+
if (this.spinner && !this.suggestSpinner) {
|
|
113
|
+
this.suggestSpinner = showSpinner.call(this as WithSpinner, this.form)
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
public finishSuggest(): void {
|
|
118
|
+
this.suggest.dispatchEvent(new CustomEvent('loaded.suggest', { bubbles: true }))
|
|
119
|
+
this.input.classList.remove(`${this.inputClassName}--loading`)
|
|
120
|
+
|
|
121
|
+
this.suggest.classList.toggle(`${this.suggestClassName}--empty`, this.isEmpty())
|
|
122
|
+
|
|
123
|
+
if (this.isEmpty() || document.activeElement !== this.input) {
|
|
124
|
+
this.hideSuggest()
|
|
125
|
+
} else {
|
|
126
|
+
this.showSuggest()
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (this.suggestSpinner) {
|
|
130
|
+
hideSpinner(this.suggestSpinner)
|
|
131
|
+
this.suggestSpinner = undefined
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
private handleInputKeydown(event: KeyboardEvent): void {
|
|
136
|
+
let anchors: HTMLAnchorElement[]
|
|
137
|
+
let activeAnchor: HTMLAnchorElement | null | undefined
|
|
138
|
+
let activeAnchorIndex: number | undefined
|
|
139
|
+
const activeClassName = `${this.linkClassName}--active`
|
|
140
|
+
|
|
141
|
+
switch (event.key) {
|
|
142
|
+
case 'Escape':
|
|
143
|
+
event.preventDefault()
|
|
144
|
+
this.hideSuggest()
|
|
145
|
+
this.input.blur()
|
|
146
|
+
break
|
|
147
|
+
|
|
148
|
+
case 'ArrowDown':
|
|
149
|
+
case 'ArrowUp':
|
|
150
|
+
event.preventDefault()
|
|
151
|
+
|
|
152
|
+
anchors = Array.from(this.suggest.querySelectorAll<HTMLAnchorElement>(`.${this.linkClassName}`))
|
|
153
|
+
activeAnchor = anchors.find((element) => element.classList.contains(activeClassName))
|
|
154
|
+
activeAnchorIndex = activeAnchor ? anchors.indexOf(activeAnchor) : undefined
|
|
155
|
+
|
|
156
|
+
activeAnchor?.classList.remove(activeClassName)
|
|
157
|
+
|
|
158
|
+
if (event.key === 'ArrowDown') {
|
|
159
|
+
activeAnchorIndex = activeAnchorIndex !== undefined ? activeAnchorIndex + 1 : 0
|
|
160
|
+
} else {
|
|
161
|
+
activeAnchorIndex = activeAnchorIndex !== undefined ? activeAnchorIndex - 1 : anchors.length - 1
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
anchors[activeAnchorIndex]?.classList.add(activeClassName)
|
|
165
|
+
|
|
166
|
+
break
|
|
167
|
+
|
|
168
|
+
case 'Enter':
|
|
169
|
+
activeAnchor = this.suggest.querySelector<HTMLAnchorElement>(`.${activeClassName}`)
|
|
170
|
+
|
|
171
|
+
if (activeAnchor) {
|
|
172
|
+
event.preventDefault()
|
|
173
|
+
event.stopPropagation()
|
|
174
|
+
|
|
175
|
+
location.href = activeAnchor.href
|
|
176
|
+
}
|
|
177
|
+
break
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
private handleInputKeyup(): void {
|
|
182
|
+
clearTimeout(this.timer)
|
|
183
|
+
|
|
184
|
+
const query = this.input.value
|
|
185
|
+
|
|
186
|
+
if (query.length < this.options.minLength) {
|
|
187
|
+
this.hideSuggest()
|
|
188
|
+
return
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if (!this.isOpen) {
|
|
192
|
+
// If the suggestion wasn't open and this query is different from the last query, we need to clear the
|
|
193
|
+
// results because they are not relevant. This only needs to be done if the suggestion has been closed.
|
|
194
|
+
if (query !== this.lastSearched) {
|
|
195
|
+
this.emptySuggest()
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
this.showSuggest()
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// If the query is the same, there's nothing more to do.
|
|
202
|
+
if (query === this.lastSearched) {
|
|
203
|
+
return
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
this.timer = setTimeout(() => {
|
|
207
|
+
this.lastSearched = query
|
|
208
|
+
this.button.click()
|
|
209
|
+
}, this.options.timeout)
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
private handleSuggestMousedown(event: MouseEvent): void {
|
|
213
|
+
event.preventDefault()
|
|
214
|
+
}
|
|
215
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { InteractionEvent } from 'naja/dist/core/UIHandler'
|
|
2
2
|
import { CompleteEvent, Extension, Naja, StartEvent } from 'naja/dist/Naja'
|
|
3
|
-
import {
|
|
3
|
+
import { SpinnerPropsFn, SpinnerType, WithSpinner } from '../types'
|
|
4
|
+
import { hideSpinner, isDatasetTruthy, showSpinner } from '../utils'
|
|
4
5
|
|
|
5
6
|
declare module 'naja/dist/Naja' {
|
|
6
7
|
interface Options {
|
|
@@ -9,15 +10,12 @@ declare module 'naja/dist/Naja' {
|
|
|
9
10
|
}
|
|
10
11
|
}
|
|
11
12
|
|
|
12
|
-
|
|
13
|
-
type spinnerPropsFn = ((initiator: Element) => any) | undefined
|
|
14
|
-
|
|
15
|
-
export class BtnSpinnerExtension implements Extension {
|
|
13
|
+
export class BtnSpinnerExtension implements Extension, WithSpinner {
|
|
16
14
|
public readonly timeout: number
|
|
17
|
-
public readonly spinner:
|
|
18
|
-
public readonly getSpinnerProps
|
|
15
|
+
public readonly spinner: SpinnerType
|
|
16
|
+
public readonly getSpinnerProps: SpinnerPropsFn
|
|
19
17
|
|
|
20
|
-
public constructor(spinner:
|
|
18
|
+
public constructor(spinner: SpinnerType, getSpinnerProps: SpinnerPropsFn = undefined, timeout = 60000) {
|
|
21
19
|
this.spinner = spinner
|
|
22
20
|
this.getSpinnerProps = getSpinnerProps
|
|
23
21
|
this.timeout = timeout
|
|
@@ -36,11 +34,11 @@ export class BtnSpinnerExtension implements Extension {
|
|
|
36
34
|
return true
|
|
37
35
|
}
|
|
38
36
|
|
|
39
|
-
const spinner =
|
|
37
|
+
const spinner = showSpinner.call(this, button)
|
|
40
38
|
|
|
41
39
|
form.dataset.btnSpinnerTimeout = String(
|
|
42
40
|
setTimeout(() => {
|
|
43
|
-
|
|
41
|
+
hideSpinner(spinner)
|
|
44
42
|
delete form.dataset.btnSpinnerTimeout
|
|
45
43
|
}, this.timeout)
|
|
46
44
|
)
|
|
@@ -70,7 +68,7 @@ export class BtnSpinnerExtension implements Extension {
|
|
|
70
68
|
return
|
|
71
69
|
}
|
|
72
70
|
|
|
73
|
-
options.btnSpinner =
|
|
71
|
+
options.btnSpinner = showSpinner.call(this, options.btnSpinnerInitiator)
|
|
74
72
|
}
|
|
75
73
|
|
|
76
74
|
private handleCompleteEvent(event: CompleteEvent): void {
|
|
@@ -80,26 +78,6 @@ export class BtnSpinnerExtension implements Extension {
|
|
|
80
78
|
return
|
|
81
79
|
}
|
|
82
80
|
|
|
83
|
-
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
private showSpinner(button: Element): Element {
|
|
87
|
-
let spinner: Element
|
|
88
|
-
|
|
89
|
-
if (typeof this.spinner === 'function') {
|
|
90
|
-
spinner = this.getSpinnerProps ? this.spinner(this.getSpinnerProps(button)) : this.spinner()
|
|
91
|
-
} else {
|
|
92
|
-
spinner = this.spinner
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
button.appendChild(spinner)
|
|
96
|
-
spinner.animate([{ opacity: 0 }, { opacity: 1 }], { duration: 100 })
|
|
97
|
-
|
|
98
|
-
return spinner
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
private hideSpinner(spinner: Element): void {
|
|
102
|
-
const animation = spinner.animate({ opacity: 0 }, { duration: 100 })
|
|
103
|
-
animation.finished.then(() => spinner?.remove())
|
|
81
|
+
hideSpinner(options.btnSpinner)
|
|
104
82
|
}
|
|
105
83
|
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { CompleteEvent, Extension, Naja, StartEvent } from 'naja/dist/Naja'
|
|
2
2
|
import { InteractionEvent } from 'naja/dist/core/UIHandler'
|
|
3
|
+
import { SpinnerPropsFn, SpinnerType, WithSpinner } from '../types'
|
|
4
|
+
import { hideSpinner, showSpinner } from '../utils'
|
|
3
5
|
|
|
4
6
|
/**
|
|
5
7
|
* @author Radek Šerý
|
|
@@ -21,19 +23,16 @@ declare module 'naja/dist/Naja' {
|
|
|
21
23
|
}
|
|
22
24
|
}
|
|
23
25
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
export class SpinnerExtension implements Extension {
|
|
28
|
-
public readonly spinner: spinnerType
|
|
29
|
-
public readonly getSpinnerProps?: spinnerPropsFn
|
|
26
|
+
export class SpinnerExtension implements Extension, WithSpinner {
|
|
27
|
+
public readonly spinner: SpinnerType
|
|
28
|
+
public readonly getSpinnerProps: SpinnerPropsFn
|
|
30
29
|
|
|
31
30
|
public readonly ajaxSpinnerWrapSelector: string
|
|
32
31
|
public readonly ajaxSpinnerPlaceholderSelector: string
|
|
33
32
|
|
|
34
33
|
public constructor(
|
|
35
|
-
spinner:
|
|
36
|
-
getSpinnerProps:
|
|
34
|
+
spinner: SpinnerType,
|
|
35
|
+
getSpinnerProps: SpinnerPropsFn = undefined,
|
|
37
36
|
ajaxSpinnerWrapSelector = '.ajax-wrap',
|
|
38
37
|
ajaxSpinnerPlaceholderSelector = '.ajax-spinner'
|
|
39
38
|
) {
|
|
@@ -71,18 +70,7 @@ export class SpinnerExtension implements Extension {
|
|
|
71
70
|
options.spinnerQueue = options.spinnerQueue || []
|
|
72
71
|
|
|
73
72
|
placeholders.forEach((placeholder) => {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
if (typeof this.spinner === 'function') {
|
|
77
|
-
spinner = this.getSpinnerProps ? this.spinner(this.getSpinnerProps(spinnerInitiator)) : this.spinner()
|
|
78
|
-
} else {
|
|
79
|
-
spinner = this.spinner
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
placeholder.appendChild(spinner)
|
|
83
|
-
options.spinnerQueue.push(spinner)
|
|
84
|
-
|
|
85
|
-
spinner.animate([{ opacity: 0 }, { opacity: 1 }], { duration: 100 })
|
|
73
|
+
options.spinnerQueue.push(showSpinner.call(this, placeholder, spinnerInitiator))
|
|
86
74
|
})
|
|
87
75
|
}
|
|
88
76
|
}
|
|
@@ -94,10 +82,7 @@ export class SpinnerExtension implements Extension {
|
|
|
94
82
|
return
|
|
95
83
|
}
|
|
96
84
|
|
|
97
|
-
options.spinnerQueue?.forEach((spinner: Element) =>
|
|
98
|
-
const animation = spinner.animate({ opacity: 0 }, { duration: 100 })
|
|
99
|
-
animation.finished.then(() => spinner.remove())
|
|
100
|
-
})
|
|
85
|
+
options.spinnerQueue?.forEach((spinner: Element) => hideSpinner(spinner))
|
|
101
86
|
}
|
|
102
87
|
|
|
103
88
|
private getPlaceholders(element: Element): Element[] {
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { InteractionEvent } from 'naja/dist/core/UIHandler'
|
|
2
|
+
import { CompleteEvent, Extension, Naja, StartEvent } from 'naja/dist/Naja'
|
|
3
|
+
import { Suggest } from '../classes/Suggest'
|
|
4
|
+
import { SpinnerPropsFn, SpinnerType } from '../types'
|
|
5
|
+
|
|
6
|
+
declare module 'naja/dist/Naja' {
|
|
7
|
+
interface Options {
|
|
8
|
+
suggest?: Suggest
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export class SuggestExtension implements Extension {
|
|
13
|
+
private requestQueue: Set<Request> = new Set()
|
|
14
|
+
|
|
15
|
+
public readonly spinner: SpinnerType | undefined
|
|
16
|
+
public readonly getSpinnerProps?: SpinnerPropsFn
|
|
17
|
+
|
|
18
|
+
public constructor(spinner: SpinnerType | undefined = undefined, getSpinnerProps: SpinnerPropsFn = undefined) {
|
|
19
|
+
this.spinner = spinner
|
|
20
|
+
this.getSpinnerProps = getSpinnerProps
|
|
21
|
+
|
|
22
|
+
const forms = document.querySelectorAll<HTMLFormElement>(`.${Suggest.className}`)
|
|
23
|
+
|
|
24
|
+
forms.forEach((form) => {
|
|
25
|
+
new Suggest(form, {}, spinner, getSpinnerProps)
|
|
26
|
+
})
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
public initialize(naja: Naja): void {
|
|
30
|
+
naja.uiHandler.addEventListener('interaction', this.checkExtensionEnabled.bind(this))
|
|
31
|
+
naja.addEventListener('start', this.start.bind(this))
|
|
32
|
+
naja.addEventListener('complete', this.complete.bind(this))
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
private checkExtensionEnabled(event: InteractionEvent): void {
|
|
36
|
+
const { element, options } = event.detail
|
|
37
|
+
|
|
38
|
+
const inputElement = element as HTMLInputElement
|
|
39
|
+
if (inputElement.form && inputElement.form._suggest) {
|
|
40
|
+
options.suggest = inputElement.form._suggest
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
private start(event: StartEvent): void {
|
|
45
|
+
const { options, request } = event.detail
|
|
46
|
+
|
|
47
|
+
if (options.suggest) {
|
|
48
|
+
this.requestQueue.add(request)
|
|
49
|
+
options.suggest.startSuggest()
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
private complete(event: CompleteEvent): void {
|
|
54
|
+
const { options, request } = event.detail
|
|
55
|
+
|
|
56
|
+
if (!options.suggest) {
|
|
57
|
+
return
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
this.requestQueue.delete(request)
|
|
61
|
+
|
|
62
|
+
if (this.requestQueue.size === 0) {
|
|
63
|
+
options.suggest.finishSuggest()
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { Extension, Naja } from 'naja/dist/Naja'
|
|
2
|
+
import { BeforeUpdateEvent } from 'naja/dist/core/SnippetHandler'
|
|
3
|
+
|
|
4
|
+
type SnippetUpdateOperation = (snippet: Element, content: string) => void
|
|
5
|
+
|
|
6
|
+
export class ViewTransitionExtension implements Extension {
|
|
7
|
+
private operation: SnippetUpdateOperation | undefined
|
|
8
|
+
|
|
9
|
+
public initialize(naja: Naja): void {
|
|
10
|
+
naja.snippetHandler.addEventListener('beforeUpdate', this.handleBeforeUpdate.bind(this))
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
private handleBeforeUpdate(event: BeforeUpdateEvent): void {
|
|
14
|
+
const { operation, changeOperation } = event.detail
|
|
15
|
+
|
|
16
|
+
if (document.startViewTransition !== undefined) {
|
|
17
|
+
this.operation = operation
|
|
18
|
+
changeOperation(this.replace.bind(this))
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
public replace(snippet: Element, content: string): ViewTransition {
|
|
23
|
+
return document.startViewTransition(() => {
|
|
24
|
+
if (!this.operation) {
|
|
25
|
+
return
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
this.operation(snippet, content)
|
|
29
|
+
this.operation = undefined
|
|
30
|
+
})
|
|
31
|
+
}
|
|
32
|
+
}
|
package/src/index.esm.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
|
|
1
|
+
export { ControlManager } from './classes/ControlManager'
|
|
2
|
+
export { Suggest } from './classes/Suggest'
|
|
2
3
|
|
|
3
4
|
export { AjaxModalExtension } from './extensions/AjaxModalExtension'
|
|
4
5
|
export { AjaxModalPreventRedrawExtension } from './extensions/AjaxModalPreventRedrawExtension'
|
|
@@ -12,8 +13,7 @@ export { ScrollToExtension } from './extensions/ScrollToExtension'
|
|
|
12
13
|
export { SingleSubmitExtension } from './extensions/SingleSubmitExtension'
|
|
13
14
|
export { SnippetFormPartExtension } from './extensions/SnippetFormPartExtension'
|
|
14
15
|
export { SpinnerExtension } from './extensions/SpinnerExtension'
|
|
16
|
+
export { SuggestExtension } from './extensions/SuggestExtension'
|
|
15
17
|
export { ToggleClassExtension } from './extensions/ToggleClassExtension'
|
|
16
18
|
|
|
17
|
-
export { isDatasetTruthy,
|
|
18
|
-
|
|
19
|
-
export const controlManager = new ControlManager()
|
|
19
|
+
export { isDatasetFalsy, isDatasetTruthy, showSpinner, hideSpinner } from './utils'
|
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
export type SpinnerType = ((props?: any) => Element) | Element
|
|
2
|
+
export type SpinnerPropsFn = ((initiator: Element) => any) | undefined
|
|
3
|
+
|
|
4
|
+
export interface WithSpinner {
|
|
5
|
+
spinner: SpinnerType
|
|
6
|
+
getSpinnerProps: SpinnerPropsFn
|
|
7
|
+
}
|
|
8
|
+
|
|
1
9
|
// `Control` is meant to be used for standalone components, that might be dependent on ajax. It should be used together
|
|
2
10
|
// with ControlManager. Class implementing the `Control` interface should export its instance. Then the intended
|
|
3
11
|
// lifecycle of class is as follows:
|
|
@@ -16,8 +24,7 @@
|
|
|
16
24
|
//
|
|
17
25
|
// 2. The `initialize` method is called for each snippet. It is called immediately after the snippet has been
|
|
18
26
|
// updated. The `context` argument is equal to the modified nette snippet.
|
|
19
|
-
|
|
20
|
-
export default interface Control {
|
|
27
|
+
export interface Control {
|
|
21
28
|
initialize(context: Element | Document): void
|
|
22
29
|
|
|
23
30
|
destroy?(context: Element): void
|
package/src/utils.ts
CHANGED
|
@@ -1,3 +1,25 @@
|
|
|
1
|
+
import { WithSpinner } from './types'
|
|
2
|
+
|
|
3
|
+
export function showSpinner(this: WithSpinner, target: Element, initiator: Element = target): Element {
|
|
4
|
+
let spinner: Element
|
|
5
|
+
|
|
6
|
+
if (typeof this.spinner === 'function') {
|
|
7
|
+
spinner = this.getSpinnerProps ? this.spinner(this.getSpinnerProps(initiator)) : this.spinner()
|
|
8
|
+
} else {
|
|
9
|
+
spinner = this.spinner
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
target.appendChild(spinner)
|
|
13
|
+
spinner.animate([{ opacity: 0 }, { opacity: 1 }], { duration: 100 })
|
|
14
|
+
|
|
15
|
+
return spinner
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function hideSpinner(spinner: Element): void {
|
|
19
|
+
const animation = spinner.animate({ opacity: 0 }, { duration: 100 })
|
|
20
|
+
animation.finished.then(() => spinner?.remove())
|
|
21
|
+
}
|
|
22
|
+
|
|
1
23
|
export const isDatasetTruthy = (element: Element, datasetName: string): boolean => {
|
|
2
24
|
const datasetValue = (element as HTMLElement).dataset[datasetName]
|
|
3
25
|
|
package/dist/utils/Control.d.ts
DELETED