@peckadesign/pd-naja 1.5.2 → 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.
@@ -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,61 @@
1
+ import { InteractionEvent } from 'naja/dist/core/UIHandler'
2
+ import { CompleteEvent, Extension, Naja, StartEvent } from 'naja/dist/Naja'
3
+
4
+ type ToggleClassRecord = Record<string, string>
5
+
6
+ type ToggleClassOptions = {
7
+ element: Element
8
+ toggleClass: ToggleClassRecord
9
+ }
10
+
11
+ declare module 'naja/dist/Naja' {
12
+ interface Options {
13
+ toggleClassOptions?: ToggleClassOptions
14
+ }
15
+ }
16
+
17
+ export class ToggleClassExtension implements Extension {
18
+ public initialize(naja: Naja) {
19
+ naja.uiHandler.addEventListener('interaction', this.checkExtensionEnabled.bind(this))
20
+ naja.addEventListener('start', this.start.bind(this))
21
+ naja.addEventListener('complete', this.complete.bind(this))
22
+ }
23
+
24
+ private checkExtensionEnabled(event: InteractionEvent): void {
25
+ const { element, options } = event.detail
26
+ const toggleClass = JSON.parse(element.getAttribute('data-naja-toggle-class') || String(null))
27
+
28
+ if (toggleClass) {
29
+ options.toggleClassOptions = {
30
+ element,
31
+ toggleClass
32
+ }
33
+ }
34
+ }
35
+
36
+ private start(event: StartEvent): void {
37
+ const { options } = event.detail
38
+
39
+ if (options.toggleClassOptions) {
40
+ this.applyToggleClass(options.toggleClassOptions)
41
+ }
42
+ }
43
+
44
+ private complete(event: CompleteEvent): void {
45
+ const { error, options } = event.detail
46
+
47
+ if (error && options.toggleClassOptions) {
48
+ this.applyToggleClass(options.toggleClassOptions)
49
+ }
50
+ }
51
+
52
+ private applyToggleClass(toggleClassOptions: ToggleClassOptions): void {
53
+ for (const [selector, classNames] of Object.entries(toggleClassOptions.toggleClass)) {
54
+ const targets = toggleClassOptions.element.querySelectorAll(selector)
55
+
56
+ targets.forEach((target) => {
57
+ classNames.split(' ').forEach((className) => target.classList.toggle(className))
58
+ })
59
+ }
60
+ }
61
+ }
@@ -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
- import ControlManager from './utils/ControlManager'
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'
@@ -8,10 +9,11 @@ export { ConfirmExtension } from './extensions/ConfirmExtension'
8
9
  export { FollowUpRequestExtension } from './extensions/FollowUpRequestExtension'
9
10
  export { ForceRedirectExtension } from './extensions/ForceRedirectExtension'
10
11
  export { ForceReplaceExtension } from './extensions/ForceReplaceExtension'
12
+ export { ScrollToExtension } from './extensions/ScrollToExtension'
11
13
  export { SingleSubmitExtension } from './extensions/SingleSubmitExtension'
12
14
  export { SnippetFormPartExtension } from './extensions/SnippetFormPartExtension'
13
15
  export { SpinnerExtension } from './extensions/SpinnerExtension'
16
+ export { SuggestExtension } from './extensions/SuggestExtension'
17
+ export { ToggleClassExtension } from './extensions/ToggleClassExtension'
14
18
 
15
- export { isDatasetTruthy, isDatasetFalsy } from './utils'
16
-
17
- 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
 
@@ -1,4 +0,0 @@
1
- export default interface Control {
2
- initialize(context: Element | Document): void;
3
- destroy?(context: Element): void;
4
- }