@peckadesign/pd-naja 1.0.0 → 1.1.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 +2 -2
- package/dist/PdNaja.esm.js +421 -0
- package/dist/PdNaja.esm.js.map +1 -0
- package/dist/extensions/AjaxModalExtension.d.ts +59 -0
- package/dist/extensions/SpinnerExtension.d.ts +33 -0
- package/dist/index.esm.d.ts +4 -0
- package/dist/index.esm.js +421 -0
- package/dist/index.esm.js.map +1 -0
- package/dist/utils/Control.d.ts +3 -0
- package/dist/utils/ControlManager.d.ts +11 -0
- package/package.json +2 -1
- package/src/extensions/AjaxModalExtension.ts +80 -32
- package/.github/workflows/ci.yml +0 -29
- package/.github/workflows/publish.yml +0 -80
- package/rollup.config.js +0 -61
|
@@ -15,6 +15,15 @@ declare module 'naja/dist/Naja' {
|
|
|
15
15
|
}
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
+
interface OptionsWithPdModal extends Options {
|
|
19
|
+
modalOpener: Element
|
|
20
|
+
modalOptions: any
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
interface PdModalHistoryState extends HistoryState {
|
|
24
|
+
pdModal: PdModalState
|
|
25
|
+
}
|
|
26
|
+
|
|
18
27
|
type CallbackFn = (callback: EventListener) => void
|
|
19
28
|
|
|
20
29
|
export interface AjaxModal {
|
|
@@ -24,7 +33,7 @@ export interface AjaxModal {
|
|
|
24
33
|
// id's of snippets, that are necessary for modal function
|
|
25
34
|
reservedSnippetIds: string[]
|
|
26
35
|
|
|
27
|
-
show(opener: Element
|
|
36
|
+
show(opener: Element, options: any, event: BeforeEvent | PopStateEvent): void
|
|
28
37
|
hide(event: SuccessEvent | PopStateEvent): void
|
|
29
38
|
isShown(): boolean
|
|
30
39
|
|
|
@@ -46,6 +55,12 @@ interface HistoryStateWrapper extends Record<string, any> {
|
|
|
46
55
|
|
|
47
56
|
type HistoryDirection = 'forwards' | 'backwards'
|
|
48
57
|
|
|
58
|
+
interface PdModalState {
|
|
59
|
+
historyDirection: HistoryDirection
|
|
60
|
+
opener: string // stringified Element
|
|
61
|
+
options: any
|
|
62
|
+
}
|
|
63
|
+
|
|
49
64
|
export class AjaxModalExtension implements Extension {
|
|
50
65
|
private readonly modal: AjaxModal
|
|
51
66
|
private readonly uniqueExtKey: string = 'modal'
|
|
@@ -56,6 +71,8 @@ export class AjaxModalExtension implements Extension {
|
|
|
56
71
|
private historyEnabled = false // (dis)allows `pushState` after hiding the modal when going back in history (popstate), we don't want to push new state into history same is true also when the history is disabled for request altogether
|
|
57
72
|
private historyDirection: HistoryDirection = 'backwards'
|
|
58
73
|
|
|
74
|
+
private shouldPreventSnippetFetch = false
|
|
75
|
+
|
|
59
76
|
private modalOptions: any = {}
|
|
60
77
|
|
|
61
78
|
private original: HistoryStateWrapper[] = [] // stack of states under the modal after hiding the modal with `forwards` history mode, we need to push the previous state
|
|
@@ -97,11 +114,15 @@ export class AjaxModalExtension implements Extension {
|
|
|
97
114
|
return options.history !== false
|
|
98
115
|
}
|
|
99
116
|
|
|
100
|
-
private
|
|
101
|
-
return
|
|
117
|
+
private isPdModalRequest = (options: Options): options is OptionsWithPdModal => {
|
|
118
|
+
return Boolean(options.pdModal)
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
private isPdModalState = (state: HistoryState | Record<string, never>): state is PdModalHistoryState => {
|
|
122
|
+
return 'pdModal' in state
|
|
102
123
|
}
|
|
103
124
|
|
|
104
|
-
private restoreExtensionPropertiesFromState = (state:
|
|
125
|
+
private restoreExtensionPropertiesFromState = (state: PdModalHistoryState): void => {
|
|
105
126
|
this.historyEnabled = true // Called from popstateHandler means the history is enabled
|
|
106
127
|
this.historyDirection = state.pdModal.historyDirection
|
|
107
128
|
this.modalOptions = state.pdModal.options
|
|
@@ -115,9 +136,7 @@ export class AjaxModalExtension implements Extension {
|
|
|
115
136
|
element.hasAttribute('data-naja-modal') ||
|
|
116
137
|
(element as HTMLInputElement).form?.hasAttribute('data-naja-modal')
|
|
117
138
|
|
|
118
|
-
|
|
119
|
-
// change when traversing ajax link inside modal.
|
|
120
|
-
if (!options.pdModal) {
|
|
139
|
+
if (!this.isPdModalRequest(options)) {
|
|
121
140
|
return
|
|
122
141
|
}
|
|
123
142
|
|
|
@@ -128,14 +147,29 @@ export class AjaxModalExtension implements Extension {
|
|
|
128
147
|
options.modalOptions = this.modalOptions
|
|
129
148
|
options.modalOpener = element
|
|
130
149
|
|
|
131
|
-
//
|
|
150
|
+
// If the extension is enabled and modal is not opened, we detect and store history mode. History mode cannot
|
|
151
|
+
// change when traversing ajax link inside modal, it stays the same until modal is hidden.
|
|
132
152
|
if (!this.modal.isShown()) {
|
|
133
153
|
this.historyDirection = element.getAttribute('data-naja-modal-history') === 'forwards' ? 'forwards' : 'backwards'
|
|
134
154
|
}
|
|
135
155
|
}
|
|
136
156
|
|
|
137
157
|
private onSnippetFetch(event: FetchEvent): void {
|
|
138
|
-
|
|
158
|
+
// When snippet cache is off, this method is called to construct Naja request options. We retrieve modal
|
|
159
|
+
// `options` from original state in history if neccessary. Also, when closing the modal from `PopstateEvent`, we
|
|
160
|
+
// don't need to make a new request, so we call `event.preventDefault()` in that case.
|
|
161
|
+
const { state } = event.detail
|
|
162
|
+
|
|
163
|
+
if (this.isPdModalState(state)) {
|
|
164
|
+
event.detail.options.pdModal = true
|
|
165
|
+
event.detail.options.modalOpener = this.getElementFromString(state.pdModal.opener)
|
|
166
|
+
event.detail.options.modalOptions = state.pdModal.options
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (this.shouldPreventSnippetFetch) {
|
|
170
|
+
event.preventDefault()
|
|
171
|
+
this.shouldPreventSnippetFetch = false
|
|
172
|
+
}
|
|
139
173
|
}
|
|
140
174
|
|
|
141
175
|
private removeModalSnippetsIds(): void {
|
|
@@ -153,7 +187,7 @@ export class AjaxModalExtension implements Extension {
|
|
|
153
187
|
|
|
154
188
|
private abortPreviousRequest(event: StartEvent): void {
|
|
155
189
|
const { abortController, options } = event.detail
|
|
156
|
-
if (options
|
|
190
|
+
if (this.isPdModalRequest(options)) {
|
|
157
191
|
this.abortControllers.get(this.uniqueExtKey)?.abort()
|
|
158
192
|
this.abortControllers.set(this.uniqueExtKey, abortController)
|
|
159
193
|
}
|
|
@@ -169,14 +203,21 @@ export class AjaxModalExtension implements Extension {
|
|
|
169
203
|
private buildState(event: BuildStateEvent): void {
|
|
170
204
|
const { options, state } = event.detail
|
|
171
205
|
|
|
172
|
-
// Every time naja builds the state, we extend
|
|
173
|
-
// opened and what history mode is in use. When options.forceRedirect
|
|
174
|
-
// state will be redirected outside it.
|
|
206
|
+
// Every time naja builds the state, and we have the modal opened, we extend the state with `pdModal` object
|
|
207
|
+
// containing information about modal being opened and what history mode is in use. When `options.forceRedirect`
|
|
208
|
+
// is set, modal might be open but the new state will be redirected outside of it.
|
|
209
|
+
//
|
|
210
|
+
// We must check this.modal.isShown instead of this.isPdModalRequest, because of initial replace state from
|
|
211
|
+
// Naja - in that case, options already contains information about request to be made, but we have to build the
|
|
212
|
+
// "previous" state yet.
|
|
175
213
|
const isShown: boolean = this.modal.isShown() && !options.forceRedirect
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
214
|
+
|
|
215
|
+
if (isShown) {
|
|
216
|
+
state.pdModal = {
|
|
217
|
+
historyDirection: this.historyDirection,
|
|
218
|
+
opener: (options.modalOpener as Element).outerHTML, // if `this.modal.isShown()` is true, the `modalOpener` has been stored
|
|
219
|
+
options: this.modalOptions
|
|
220
|
+
}
|
|
180
221
|
}
|
|
181
222
|
|
|
182
223
|
// If the state is build, the history is enabled. This information is needed inside modal callback where the
|
|
@@ -186,7 +227,7 @@ export class AjaxModalExtension implements Extension {
|
|
|
186
227
|
|
|
187
228
|
private before(event: BeforeEvent) {
|
|
188
229
|
const { options, request } = event.detail
|
|
189
|
-
if (!options
|
|
230
|
+
if (!this.isPdModalRequest(options)) {
|
|
190
231
|
return
|
|
191
232
|
}
|
|
192
233
|
|
|
@@ -205,7 +246,7 @@ export class AjaxModalExtension implements Extension {
|
|
|
205
246
|
title: document.title
|
|
206
247
|
}
|
|
207
248
|
|
|
208
|
-
if (!options
|
|
249
|
+
if (!this.isPdModalRequest(options)) {
|
|
209
250
|
return
|
|
210
251
|
}
|
|
211
252
|
|
|
@@ -300,6 +341,7 @@ export class AjaxModalExtension implements Extension {
|
|
|
300
341
|
|
|
301
342
|
const isCurrentStatePdModal = this.isPdModalState(state)
|
|
302
343
|
this.popstateFlag = true
|
|
344
|
+
this.shouldPreventSnippetFetch = false
|
|
303
345
|
|
|
304
346
|
// We don't know how many states we go back. So we go one by one until the new state is not modal state
|
|
305
347
|
// (`isPdModalState` is `false`).
|
|
@@ -321,17 +363,17 @@ export class AjaxModalExtension implements Extension {
|
|
|
321
363
|
this.hidePopstateFlag = false
|
|
322
364
|
}
|
|
323
365
|
|
|
324
|
-
// We check if the state has pdModal object present on popstate. If so
|
|
325
|
-
//
|
|
366
|
+
// We check if the state has `pdModal` object present on popstate. If so , we proceed to open the modal. Content
|
|
367
|
+
// of the modal is restored by naja itself (either from cache or by new request).
|
|
326
368
|
//
|
|
327
|
-
// If the initial state is also detected as pdModal state, we returned to some pdModal state using reload. In
|
|
328
|
-
// case, we don't want to open the modal, because we might be missing some snippets. Effectively this means
|
|
329
|
-
// modal will never be opened by forward / back button if there has been some other site loaded outside
|
|
330
|
-
// (e.g. some non-ajax link leading from modal).
|
|
369
|
+
// If the initial state is also detected as pdModal state, we returned to some pdModal state using reload. In
|
|
370
|
+
// that case, we don't want to open the modal, because we might be missing some snippets. Effectively this means
|
|
371
|
+
// that the modal will never be opened by forward / back button if there has been some other site loaded outside
|
|
372
|
+
// the modal (e.g. some non-ajax link leading from modal).
|
|
331
373
|
if (isCurrentStatePdModal && !this.isPdModalState(this.initialState)) {
|
|
332
374
|
this.restoreExtensionPropertiesFromState(state)
|
|
333
375
|
|
|
334
|
-
this.modal.show(
|
|
376
|
+
this.modal.show(this.getElementFromString(state.pdModal.opener), state.pdModal.options, event)
|
|
335
377
|
|
|
336
378
|
// If there is some snippet cache, we might restore modal options. If not, options will be restored based on
|
|
337
379
|
// options after the ajax request. Same applies to dispatching load event - if cache is on, we dispatch the
|
|
@@ -346,16 +388,18 @@ export class AjaxModalExtension implements Extension {
|
|
|
346
388
|
} else {
|
|
347
389
|
this.historyEnabled = false // Hiding modal using forward / back button, we disable the history to prevent state duplication
|
|
348
390
|
|
|
349
|
-
// Reload the page if the initial state has been inside modal. This prevents snippets loss e.g. during
|
|
391
|
+
// Reload the page if the initial state has been inside modal. This prevents snippets loss e.g. during
|
|
392
|
+
// layout changes.
|
|
350
393
|
if (this.isPdModalState(this.initialState)) {
|
|
351
394
|
window.location.reload()
|
|
352
395
|
}
|
|
353
396
|
|
|
354
|
-
// Non-modal state and non-modal initial state
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
397
|
+
// Non-modal state and non-modal initial state → we just hide the current modal and prevent snippet fetch
|
|
398
|
+
// (if snippet cache is off).
|
|
399
|
+
if (this.modal.isShown()) {
|
|
400
|
+
this.modal.hide(event)
|
|
401
|
+
this.shouldPreventSnippetFetch = true
|
|
402
|
+
}
|
|
359
403
|
}
|
|
360
404
|
|
|
361
405
|
// Keep track of current state. When `backwards` history mode is used, we eventually push this state into
|
|
@@ -372,4 +416,8 @@ export class AjaxModalExtension implements Extension {
|
|
|
372
416
|
this.historyDirection = 'backwards'
|
|
373
417
|
this.modalOptions = null
|
|
374
418
|
}
|
|
419
|
+
|
|
420
|
+
private getElementFromString(stringElement: string): Element {
|
|
421
|
+
return new DOMParser().parseFromString(stringElement, 'text/html').body.firstElementChild as Element
|
|
422
|
+
}
|
|
375
423
|
}
|
package/.github/workflows/ci.yml
DELETED
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
name: CI
|
|
2
|
-
on:
|
|
3
|
-
pull_request:
|
|
4
|
-
types: [opened, synchronize]
|
|
5
|
-
|
|
6
|
-
jobs:
|
|
7
|
-
lint:
|
|
8
|
-
runs-on: ubuntu-latest
|
|
9
|
-
name: ESLint
|
|
10
|
-
env:
|
|
11
|
-
CI: true
|
|
12
|
-
steps:
|
|
13
|
-
- name: Checkout 🛎
|
|
14
|
-
uses: actions/checkout@v3
|
|
15
|
-
|
|
16
|
-
- name: Setup Node 📦
|
|
17
|
-
uses: actions/setup-node@v3
|
|
18
|
-
with:
|
|
19
|
-
node-version: lts/*
|
|
20
|
-
cache: npm
|
|
21
|
-
|
|
22
|
-
- name: Install dependencies 👨🏻💻
|
|
23
|
-
run: npm ci
|
|
24
|
-
|
|
25
|
-
- name: Lint ✨
|
|
26
|
-
run: npm run lint
|
|
27
|
-
|
|
28
|
-
- name: Build 🔨
|
|
29
|
-
run: npm run build
|
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
name: Publish
|
|
2
|
-
on:
|
|
3
|
-
push:
|
|
4
|
-
tags:
|
|
5
|
-
- '*'
|
|
6
|
-
|
|
7
|
-
jobs:
|
|
8
|
-
build:
|
|
9
|
-
name: Build
|
|
10
|
-
runs-on: ubuntu-latest
|
|
11
|
-
steps:
|
|
12
|
-
- name: Checkout 🛎
|
|
13
|
-
uses: actions/checkout@v3
|
|
14
|
-
|
|
15
|
-
- name: Setup Node 📦
|
|
16
|
-
uses: actions/setup-node@v3
|
|
17
|
-
with:
|
|
18
|
-
node-version: lts/*
|
|
19
|
-
cache: npm
|
|
20
|
-
|
|
21
|
-
- name: Install dependencies 👨🏻💻
|
|
22
|
-
run: npm ci
|
|
23
|
-
|
|
24
|
-
- name: Build 🔨
|
|
25
|
-
run: npm run build
|
|
26
|
-
|
|
27
|
-
- name: Download artifacts 🧩
|
|
28
|
-
uses: actions/upload-artifact@v3
|
|
29
|
-
with:
|
|
30
|
-
name: dist-files
|
|
31
|
-
path: dist/
|
|
32
|
-
|
|
33
|
-
release:
|
|
34
|
-
name: Release
|
|
35
|
-
runs-on: ubuntu-latest
|
|
36
|
-
needs: [build]
|
|
37
|
-
steps:
|
|
38
|
-
- name: Checkout 🛎
|
|
39
|
-
uses: actions/checkout@v3
|
|
40
|
-
|
|
41
|
-
- name: Download artifacts 🧩
|
|
42
|
-
uses: actions/download-artifact@v3
|
|
43
|
-
with:
|
|
44
|
-
name: dist-files
|
|
45
|
-
path: dist/
|
|
46
|
-
|
|
47
|
-
- name: Create release draft 🕊️
|
|
48
|
-
uses: softprops/action-gh-release@v1
|
|
49
|
-
with:
|
|
50
|
-
draft: true
|
|
51
|
-
files: |
|
|
52
|
-
dist/PdModal.js
|
|
53
|
-
dist/PdModal.js.map
|
|
54
|
-
dist/PdModal.min.js
|
|
55
|
-
dist/PdModal.min.js.map
|
|
56
|
-
|
|
57
|
-
publish:
|
|
58
|
-
name: Publish
|
|
59
|
-
runs-on: ubuntu-latest
|
|
60
|
-
needs: [build]
|
|
61
|
-
steps:
|
|
62
|
-
- name: Checkout 🛎
|
|
63
|
-
uses: actions/checkout@v3
|
|
64
|
-
|
|
65
|
-
- name: Download artifacts 🧩
|
|
66
|
-
uses: actions/download-artifact@v3
|
|
67
|
-
with:
|
|
68
|
-
name: dist-files
|
|
69
|
-
path: dist/
|
|
70
|
-
|
|
71
|
-
- name: Setup Node 📦
|
|
72
|
-
uses: actions/setup-node@v3
|
|
73
|
-
with:
|
|
74
|
-
node-version: lts/*
|
|
75
|
-
registry-url: 'https://registry.npmjs.org'
|
|
76
|
-
|
|
77
|
-
- name: Publish release 🕊️
|
|
78
|
-
run: npm publish --access public
|
|
79
|
-
env:
|
|
80
|
-
NODE_AUTH_TOKEN: ${{ secrets.NPM_AUTH_TOKEN }}
|
package/rollup.config.js
DELETED
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
import babel from '@rollup/plugin-babel';
|
|
2
|
-
import commonjs from '@rollup/plugin-commonjs';
|
|
3
|
-
import resolve from '@rollup/plugin-node-resolve';
|
|
4
|
-
import typescript from '@rollup/plugin-typescript';
|
|
5
|
-
import path from 'path';
|
|
6
|
-
|
|
7
|
-
import pkg from './package.json';
|
|
8
|
-
const output = {
|
|
9
|
-
banner: `/**\n * ${pkg.title} - ${pkg.description} \n * ${pkg.homepage}\n *\n * @author ${pkg.author.name} <${pkg.author.email}>\n * @license ${pkg.license}\n *\n * @version ${pkg.version}\n */\n`,
|
|
10
|
-
sourcemap: true,
|
|
11
|
-
};
|
|
12
|
-
|
|
13
|
-
const babelPlugin = babel({
|
|
14
|
-
exclude: /node_modules/,
|
|
15
|
-
include: 'src/**',
|
|
16
|
-
babelHelpers: 'runtime',
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
export default [
|
|
20
|
-
{
|
|
21
|
-
// ESM build for modern tools like webpack
|
|
22
|
-
input: 'src/index.esm.ts',
|
|
23
|
-
output: {
|
|
24
|
-
...output,
|
|
25
|
-
file: pkg.module,
|
|
26
|
-
format: 'esm',
|
|
27
|
-
},
|
|
28
|
-
external: [
|
|
29
|
-
/@babel\/runtime/,
|
|
30
|
-
...Object.keys(pkg.dependencies || {}),
|
|
31
|
-
...Object.keys(pkg.peerDependencies || {}),
|
|
32
|
-
],
|
|
33
|
-
plugins: [
|
|
34
|
-
resolve(),
|
|
35
|
-
commonjs(),
|
|
36
|
-
typescript(),
|
|
37
|
-
babelPlugin,
|
|
38
|
-
],
|
|
39
|
-
},
|
|
40
|
-
{
|
|
41
|
-
// type declaration files for ESM build
|
|
42
|
-
input: 'src/index.esm.ts',
|
|
43
|
-
output: {
|
|
44
|
-
...output,
|
|
45
|
-
dir: path.dirname(pkg.module),
|
|
46
|
-
format: 'esm',
|
|
47
|
-
},
|
|
48
|
-
external: [
|
|
49
|
-
/@babel\/runtime/,
|
|
50
|
-
...Object.keys(pkg.dependencies || {}),
|
|
51
|
-
...Object.keys(pkg.peerDependencies || {}),
|
|
52
|
-
],
|
|
53
|
-
plugins: [
|
|
54
|
-
typescript({
|
|
55
|
-
declaration: true,
|
|
56
|
-
declarationDir: path.dirname(pkg.module),
|
|
57
|
-
emitDeclarationOnly: true,
|
|
58
|
-
}),
|
|
59
|
-
],
|
|
60
|
-
},
|
|
61
|
-
];
|