@projektemacher/storymapjs 0.10.5 → 0.10.7
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/CHANGELOG +11 -0
- package/dist/assets/{demo-CVQ7kFek.js → demo-D6cTmzn6.js} +1 -1
- package/dist/assets/{harness-C-VZV-ou.js → harness-BPFlzutp.js} +1 -1
- package/dist/assets/main-Y-MYUSJ4.js +17 -0
- package/dist/css/fonts/font.oldstandard.css +14 -32
- package/dist/demo.html +2 -2
- package/dist/docs/migration.html +1 -1
- package/dist/embed/index.html +129 -4
- package/dist/harness.html +2 -2
- package/dist/index.html +17 -0
- package/dist/js/storymap.d.ts +126 -7
- package/dist/js/storymap.js +4518 -3613
- package/dist/js/storymap.js.map +1 -1
- package/docs/migration-from-knightlab.md +139 -0
- package/docs/storymap-as-iiif-manifest.md +510 -0
- package/package.json +6 -3
- package/src/animation/easings.ts +18 -0
- package/src/animation/morpheus.d.ts +41 -0
- package/src/core/Browser.ts +19 -0
- package/src/core/Load.ts +180 -0
- package/src/core/Util.ts +354 -0
- package/src/core/mixins.ts +171 -0
- package/src/dom/Dom.ts +31 -0
- package/src/dom/DomEvent.ts +61 -0
- package/src/globals.d.ts +16 -0
- package/src/language/Language.ts +107 -0
- package/src/language/locale/be.json +18 -0
- package/src/language/locale/bg.json +18 -0
- package/src/language/locale/cs.json +18 -0
- package/src/language/locale/de.json +20 -0
- package/src/language/locale/el.json +18 -0
- package/src/language/locale/en.json +28 -0
- package/src/language/locale/es.json +18 -0
- package/src/language/locale/et.json +18 -0
- package/src/language/locale/fr.json +18 -0
- package/src/language/locale/he.json +18 -0
- package/src/language/locale/hu.json +18 -0
- package/src/language/locale/is.json +18 -0
- package/src/language/locale/it.json +18 -0
- package/src/language/locale/jp.json +18 -0
- package/src/language/locale/ko.json +18 -0
- package/src/language/locale/nl.json +18 -0
- package/src/language/locale/nn.json +18 -0
- package/src/language/locale/no.json +18 -0
- package/src/language/locale/pl.json +18 -0
- package/src/language/locale/pt.json +18 -0
- package/src/language/locale/ru.json +18 -0
- package/src/language/locale/sk.json +18 -0
- package/src/language/locale/sr.json +17 -0
- package/src/language/locale/sv.json +18 -0
- package/src/language/locale/tr.json +18 -0
- package/src/language/locale/uk.json +18 -0
- package/src/language/locale/ur.json +18 -0
- package/src/language/locale/zh-cn.json +18 -0
- package/src/language/locale/zh-tw.json +18 -0
- package/src/main.ts +51 -0
- package/src/map/Map.ts +717 -0
- package/src/map/MapMarker.ts +149 -0
- package/src/map/openlayers/Map.OpenLayers.ts +1879 -0
- package/src/map/openlayers/MapMarker.OpenLayers.ts +176 -0
- package/src/map/openlayers/zoomifyTiles.ts +28 -0
- package/src/map/types.ts +17 -0
- package/src/media/EmbedUtil.ts +168 -0
- package/src/media/Media.ts +409 -0
- package/src/media/MediaType.ts +163 -0
- package/src/media/types/Audio.ts +87 -0
- package/src/media/types/Blockquote.ts +40 -0
- package/src/media/types/DailyMotion.ts +58 -0
- package/src/media/types/DocumentCloud.ts +41 -0
- package/src/media/types/Facebook.ts +41 -0
- package/src/media/types/Flickr.ts +115 -0
- package/src/media/types/GoogleDoc.ts +59 -0
- package/src/media/types/IFrame.ts +51 -0
- package/src/media/types/Image.ts +49 -0
- package/src/media/types/Juxtapose.ts +32 -0
- package/src/media/types/SoundCloud.ts +77 -0
- package/src/media/types/Text.ts +172 -0
- package/src/media/types/Twitter.ts +96 -0
- package/src/media/types/Video.ts +84 -0
- package/src/media/types/Vimeo.ts +65 -0
- package/src/media/types/Website.ts +36 -0
- package/src/media/types/Wikipedia.ts +124 -0
- package/src/media/types/YouTube.ts +192 -0
- package/src/scss/Typography.scss +237 -0
- package/src/scss/VCO.StoryMap.Dark.scss +138 -0
- package/src/scss/VCO.StoryMap.scss +164 -0
- package/src/scss/Variables.Dark.scss +56 -0
- package/src/scss/Variables.scss +47 -0
- package/src/scss/core/Mixins.scss +112 -0
- package/src/scss/core/Reset.scss +206 -0
- package/src/scss/fonts/_font.base.scss +113 -0
- package/src/scss/fonts/font.abril-droidsans.scss +75 -0
- package/src/scss/fonts/font.amatic-andika.scss +73 -0
- package/src/scss/fonts/font.bitter-raleway.scss +86 -0
- package/src/scss/fonts/font.clicker-garamond.scss +92 -0
- package/src/scss/fonts/font.dancing-ledger.scss +74 -0
- package/src/scss/fonts/font.default.scss +9 -0
- package/src/scss/fonts/font.fjalla-average.scss +80 -0
- package/src/scss/fonts/font.georgia-helvetica.scss +58 -0
- package/src/scss/fonts/font.knightlab.scss +97 -0
- package/src/scss/fonts/font.lustria-lato.scss +80 -0
- package/src/scss/fonts/font.medula-lato.scss +73 -0
- package/src/scss/fonts/font.oldstandard.scss +88 -0
- package/src/scss/fonts/font.opensans-gentiumbook.scss +80 -0
- package/src/scss/fonts/font.playfair-faunaone.scss +86 -0
- package/src/scss/fonts/font.playfair.scss +85 -0
- package/src/scss/fonts/font.pt.scss +90 -0
- package/src/scss/fonts/font.roboto-megrim.scss +73 -0
- package/src/scss/fonts/font.rufina-sintony.scss +73 -0
- package/src/scss/fonts/font.ubuntu.scss +78 -0
- package/src/scss/fonts/font.unicaone-vollkorn.scss +85 -0
- package/src/scss/icons/Icons.scss +239 -0
- package/src/scss/map/VCO.ImageMarker.scss +23 -0
- package/src/scss/map/VCO.Map.scss +21 -0
- package/src/scss/map/VCO.MapMarker.scss +147 -0
- package/src/scss/map/openlayers/VCO.Map.OpenLayers.scss +126 -0
- package/src/scss/media/VCO.Media.scss +222 -0
- package/src/scss/media/types/VCO.Media.Blockquote.scss +76 -0
- package/src/scss/media/types/VCO.Media.IFrame.scss +11 -0
- package/src/scss/media/types/VCO.Media.Image.scss +13 -0
- package/src/scss/media/types/VCO.Media.Instagram.scss +7 -0
- package/src/scss/media/types/VCO.Media.Profile.scss +5 -0
- package/src/scss/media/types/VCO.Media.Text.scss +90 -0
- package/src/scss/media/types/VCO.Media.Twitter.scss +87 -0
- package/src/scss/media/types/VCO.Media.Wikipedia.scss +64 -0
- package/src/scss/site/site.scss +748 -0
- package/src/scss/slider/VCO.Slide.scss +447 -0
- package/src/scss/slider/VCO.SlideNav.scss +338 -0
- package/src/scss/slider/VCO.StorySlider.scss +64 -0
- package/src/scss/snapmap/VCO.SnapMap.scss +103 -0
- package/src/scss/ui/VCO.Consent.scss +77 -0
- package/src/scss/ui/VCO.MenuBar.Button.scss +99 -0
- package/src/scss/ui/VCO.MenuBar.scss +20 -0
- package/src/scss/ui/VCO.Message.scss +104 -0
- package/src/site/site.ts +87 -0
- package/src/slider/Slide.ts +460 -0
- package/src/slider/SlideNav.ts +212 -0
- package/src/slider/StorySlider.ts +722 -0
- package/src/storymap/Consent.ts +146 -0
- package/src/storymap/StoryMap.ts +1108 -0
- package/src/storymap/iiif.ts +386 -0
- package/src/storymap/validate.ts +184 -0
- package/src/types.ts +297 -0
- package/src/ui/Draggable.ts +374 -0
- package/src/ui/MenuBar.ts +323 -0
- package/src/ui/Message.ts +118 -0
- package/src/ui/Swipable.ts +388 -0
- package/dist/assets/main-DwbXrrYn.js +0 -17
package/src/types.ts
ADDED
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
// Shared types for the StoryMapJS codebase.
|
|
2
|
+
|
|
3
|
+
// ---------- Storymap data (exchange format) ----------
|
|
4
|
+
|
|
5
|
+
export interface StorymapSlideText {
|
|
6
|
+
headline?: string;
|
|
7
|
+
text?: string;
|
|
8
|
+
text_align?: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface StorymapSlideLocation {
|
|
12
|
+
lat?: number;
|
|
13
|
+
lon?: number;
|
|
14
|
+
zoom?: number;
|
|
15
|
+
line?: boolean;
|
|
16
|
+
icon?: string;
|
|
17
|
+
iconSize?: number[];
|
|
18
|
+
image?: string;
|
|
19
|
+
use_custom_marker?: boolean;
|
|
20
|
+
[key: string]: unknown;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export type MediaTypeMatch = {
|
|
24
|
+
type: string;
|
|
25
|
+
name: string;
|
|
26
|
+
match_str: string;
|
|
27
|
+
// media type class constructor
|
|
28
|
+
cls: new (data: StorymapSlideMedia, options: Record<string, unknown>) => unknown;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export interface StorymapSlideMedia {
|
|
32
|
+
url?: string | null;
|
|
33
|
+
caption?: string | null;
|
|
34
|
+
credit?: string | null;
|
|
35
|
+
thumb?: string | null;
|
|
36
|
+
mediatype?: MediaTypeMatch | null;
|
|
37
|
+
[key: string]: unknown;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface StorymapSlideBackground {
|
|
41
|
+
url?: string | null;
|
|
42
|
+
color?: string | null;
|
|
43
|
+
opacity?: number;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface StorymapSlide {
|
|
47
|
+
type?: string;
|
|
48
|
+
date?: string | Record<string, unknown> | null;
|
|
49
|
+
group?: string;
|
|
50
|
+
location?: StorymapSlideLocation;
|
|
51
|
+
media?: StorymapSlideMedia;
|
|
52
|
+
text?: StorymapSlideText;
|
|
53
|
+
background?: StorymapSlideBackground | string | null;
|
|
54
|
+
uniqueid?: string;
|
|
55
|
+
[key: string]: unknown;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export interface StorymapData {
|
|
59
|
+
uniqueid?: string;
|
|
60
|
+
slides: StorymapSlide[];
|
|
61
|
+
[key: string]: unknown;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export interface StorymapDataWrapper {
|
|
65
|
+
storymap: StorymapData;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// ---------- StoryMap / Map options ----------
|
|
69
|
+
|
|
70
|
+
export interface StorymapOptions {
|
|
71
|
+
width: number;
|
|
72
|
+
height: number;
|
|
73
|
+
layout: string;
|
|
74
|
+
base_class: string;
|
|
75
|
+
default_bg_color: { r: number; g: number; b: number };
|
|
76
|
+
map_size_sticky: number;
|
|
77
|
+
map_center_offset: { left: number; top: number } | null;
|
|
78
|
+
less_bounce: boolean;
|
|
79
|
+
start_at_slide: number;
|
|
80
|
+
call_to_action: boolean;
|
|
81
|
+
call_to_action_text: string;
|
|
82
|
+
menubar_height: number;
|
|
83
|
+
/** Show a fullscreen toggle button in the menubar (default: true) */
|
|
84
|
+
fullscreen: boolean;
|
|
85
|
+
/** Show the map overview button in the menubar (default: true) */
|
|
86
|
+
show_overview: boolean;
|
|
87
|
+
/** Show the back-to-the-beginning button in the menubar (default: true) */
|
|
88
|
+
show_back_to_start: boolean;
|
|
89
|
+
skinny_size: number;
|
|
90
|
+
duration: number;
|
|
91
|
+
ease: unknown;
|
|
92
|
+
dragging: boolean;
|
|
93
|
+
trackResize: boolean;
|
|
94
|
+
/**
|
|
95
|
+
* Navigate slides with arrow keys anywhere on the page (default false:
|
|
96
|
+
* arrows only work when the slide panel has focus). Form elements and
|
|
97
|
+
* the map itself (which pans) are always skipped. Read at construction.
|
|
98
|
+
*/
|
|
99
|
+
keyboard: boolean;
|
|
100
|
+
/** Re-fetch the source file on every load, bypassing caches (issue #417) */
|
|
101
|
+
nocache: boolean;
|
|
102
|
+
/** Advance slides automatically every N milliseconds; 0 disables (issue #380) */
|
|
103
|
+
autoplay: number;
|
|
104
|
+
/** Show a progress bar in the menubar (issue #247) */
|
|
105
|
+
show_progress: boolean;
|
|
106
|
+
/** Show the slide headline as a label on the active map marker (issue #243) */
|
|
107
|
+
marker_labels: boolean;
|
|
108
|
+
/** Default text alignment for slide text: left, center or right (issue #244) */
|
|
109
|
+
text_align: "left" | "center" | "right";
|
|
110
|
+
/** Override the overview fit center (issues #107, #271) */
|
|
111
|
+
map_overview_center: { lat: number; lon: number } | null;
|
|
112
|
+
map_type: string;
|
|
113
|
+
attribution: string;
|
|
114
|
+
/**
|
|
115
|
+
* Stacked raster overlays above the base map, below the route lines.
|
|
116
|
+
* Each entry accepts any `map_type` value (XYZ template, `osm:style`,
|
|
117
|
+
* …) plus per-layer presentation; see StorymapOverlayLayer. Empty
|
|
118
|
+
* (default) means base map only.
|
|
119
|
+
*/
|
|
120
|
+
overlays: StorymapOverlayLayer[];
|
|
121
|
+
map_mini: boolean;
|
|
122
|
+
map_subdomains: string;
|
|
123
|
+
map_as_image: boolean;
|
|
124
|
+
map_access_token: string;
|
|
125
|
+
map_background_color: string;
|
|
126
|
+
/**
|
|
127
|
+
* Landscape map layout: `"full"` (default) spans the whole width with the
|
|
128
|
+
* slide panel fading in over it (the map view is offset so markers clear
|
|
129
|
+
* the panel); `"left"` limits the map to the left, visible half with an
|
|
130
|
+
* opaque slide panel — no offset needed, fits and constraints align with
|
|
131
|
+
* the visible area directly. Portrait layouts are unaffected.
|
|
132
|
+
*/
|
|
133
|
+
map_area: "full" | "left";
|
|
134
|
+
/**
|
|
135
|
+
* Limit the map to a bounding box `[west, south, east, north]` (lon/lat;
|
|
136
|
+
* raw image pixel coordinates for image-space maps). The view center is
|
|
137
|
+
* constrained to the box; `null` (default) leaves the map unconstrained.
|
|
138
|
+
*/
|
|
139
|
+
map_bbox: number[] | null;
|
|
140
|
+
/**
|
|
141
|
+
* Constrain the minimap overview to a bounding box `[west, south, east,
|
|
142
|
+
* north]` in lon/lat (mercator maps only); `null` (default) leaves the
|
|
143
|
+
* overview unconstrained.
|
|
144
|
+
*/
|
|
145
|
+
overview_extent: [number, number, number, number] | null;
|
|
146
|
+
/**
|
|
147
|
+
* Legacy zoomify image pyramid (map_type: "zoomify"): the tiles are
|
|
148
|
+
* placed at the standard mercator tile positions, stretched from the
|
|
149
|
+
* world's top-left corner.
|
|
150
|
+
*/
|
|
151
|
+
zoomify?:
|
|
152
|
+
| {
|
|
153
|
+
path?: string;
|
|
154
|
+
width?: number;
|
|
155
|
+
height?: number;
|
|
156
|
+
tolerance?: number;
|
|
157
|
+
attribution?: string;
|
|
158
|
+
}
|
|
159
|
+
| boolean;
|
|
160
|
+
/**
|
|
161
|
+
* Ask for permission before loading anything from external services
|
|
162
|
+
* (media embeds, map tiles, external font CSS) — GDPR consent mode.
|
|
163
|
+
* Grants are remembered per service in a cookie for 90 days.
|
|
164
|
+
*/
|
|
165
|
+
consent_required: boolean;
|
|
166
|
+
/** Slide text color override, sets --vco-color-text (issue #177) */
|
|
167
|
+
text_color: string;
|
|
168
|
+
/** Slide panel background color override (issue #177) */
|
|
169
|
+
text_background_color: string;
|
|
170
|
+
/** Show the great-circle route distance in the menubar (issue #341) */
|
|
171
|
+
show_distance: boolean;
|
|
172
|
+
/**
|
|
173
|
+
* Raw OpenLayers map configuration. `controls` and `interactions` replace
|
|
174
|
+
* the StoryMapJS defaults, `view` is merged over the computed default view
|
|
175
|
+
* and `element` (an HTMLElement or DOM id) replaces the auto-created map
|
|
176
|
+
* container div.
|
|
177
|
+
*/
|
|
178
|
+
map_options: StorymapMapOptions;
|
|
179
|
+
map_popup: boolean;
|
|
180
|
+
zoom_distance: number;
|
|
181
|
+
calculate_zoom: boolean;
|
|
182
|
+
line_follows_path: boolean;
|
|
183
|
+
line_color: string;
|
|
184
|
+
line_color_inactive: string;
|
|
185
|
+
line_join: string;
|
|
186
|
+
line_weight: number;
|
|
187
|
+
line_opacity: number;
|
|
188
|
+
line_dash: string;
|
|
189
|
+
show_lines: boolean;
|
|
190
|
+
show_history_line: boolean;
|
|
191
|
+
use_custom_markers: boolean;
|
|
192
|
+
iiif: { url: string; attribution: string };
|
|
193
|
+
map_height: number;
|
|
194
|
+
storyslider_height: number;
|
|
195
|
+
slide_padding_lr: number;
|
|
196
|
+
slide_default_fade: string;
|
|
197
|
+
menubar_default_y: number;
|
|
198
|
+
path_gfx: string;
|
|
199
|
+
script_path: string;
|
|
200
|
+
font_css: string;
|
|
201
|
+
language: string;
|
|
202
|
+
api_key_flickr: string;
|
|
203
|
+
[key: string]: unknown;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// ---------- animation ----------
|
|
207
|
+
|
|
208
|
+
export interface AnimateOptions extends Record<string, unknown> {
|
|
209
|
+
duration?: number;
|
|
210
|
+
easing?: unknown;
|
|
211
|
+
complete?: unknown;
|
|
212
|
+
left?: string | number;
|
|
213
|
+
top?: string | number;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
export interface AnimationHandle {
|
|
217
|
+
stop: (jump?: boolean) => void;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// ---------- map ----------
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* A stacked raster overlay above the base map (StorymapOptions.overlays).
|
|
224
|
+
* Presentation is declarative: hosts no longer need to reach into the
|
|
225
|
+
* layer objects for blend modes, clips or stacking tweaks.
|
|
226
|
+
*/
|
|
227
|
+
export interface StorymapOverlayLayer {
|
|
228
|
+
/** Any `map_type` value the tile layer factory accepts */
|
|
229
|
+
map_type: string;
|
|
230
|
+
/** Layer opacity 0..1 (default 1) */
|
|
231
|
+
opacity?: number;
|
|
232
|
+
/** Initial visibility (default true) */
|
|
233
|
+
visible?: boolean;
|
|
234
|
+
/** Extra attribution fragment, listed while the overlay is visible */
|
|
235
|
+
attribution?: string;
|
|
236
|
+
/**
|
|
237
|
+
* CSS class for the layer container. OpenLayers paints every layer
|
|
238
|
+
* with the same class into one shared div/canvas, so a distinct
|
|
239
|
+
* class isolates this layer (e.g. for blend modes). The class
|
|
240
|
+
* replaces the default wholesale, so keep the `ol-layer` token
|
|
241
|
+
* (e.g. `"ol-layer historic-sheet"`) unless you know why not.
|
|
242
|
+
*/
|
|
243
|
+
className?: string;
|
|
244
|
+
/** CSS mix-blend-mode for the layer container (needs className) */
|
|
245
|
+
blendMode?: string;
|
|
246
|
+
/** Clip box `[west, south, east, north]` in lon/lat (mercator maps) */
|
|
247
|
+
extent?: [number, number, number, number];
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/** OpenLayers passthrough options (see StorymapOptions.map_options) */
|
|
251
|
+
export interface StorymapMapOptions {
|
|
252
|
+
/** HTMLElement or DOM id that becomes the real map container */
|
|
253
|
+
element?: HTMLElement | string;
|
|
254
|
+
/** Merged over the default view configuration */
|
|
255
|
+
view?: Record<string, unknown>;
|
|
256
|
+
/** Replaces the default (empty) controls list */
|
|
257
|
+
controls?: unknown[];
|
|
258
|
+
/** Replaces the default (empty) interactions list */
|
|
259
|
+
interactions?: unknown[];
|
|
260
|
+
/** Any other ol/Map constructor option (layers, pixelRatio, ...) */
|
|
261
|
+
[key: string]: unknown;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
export interface LatLngLiteral {
|
|
265
|
+
lat: number | undefined;
|
|
266
|
+
lon?: number | undefined;
|
|
267
|
+
lng?: number | undefined;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
export interface MapMarkerData {
|
|
271
|
+
real_marker?: boolean;
|
|
272
|
+
location?: StorymapSlideLocation;
|
|
273
|
+
media?: StorymapSlideMedia & { mediatype?: MediaTypeMatch | null };
|
|
274
|
+
text?: { headline?: string };
|
|
275
|
+
type?: string;
|
|
276
|
+
[key: string]: unknown;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
export type IconSpec = {
|
|
280
|
+
url: string;
|
|
281
|
+
size: number[];
|
|
282
|
+
anchor: number[];
|
|
283
|
+
};
|
|
284
|
+
|
|
285
|
+
// ---------- media ----------
|
|
286
|
+
|
|
287
|
+
export interface MediaState {
|
|
288
|
+
loaded: boolean;
|
|
289
|
+
[key: string]: unknown;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
export interface LanguageStrings {
|
|
293
|
+
[key: string]: unknown;
|
|
294
|
+
loading: string;
|
|
295
|
+
wikipedia: string;
|
|
296
|
+
start: string;
|
|
297
|
+
}
|
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
import { mergeData } from "../core/Util";
|
|
2
|
+
import { Evented, type EventedInstance } from "../core/mixins";
|
|
3
|
+
import Dom from "../dom/Dom";
|
|
4
|
+
import { DomEvent, type LegacyEvent } from "../dom/DomEvent";
|
|
5
|
+
import { touch } from "../core/Browser";
|
|
6
|
+
import { easeInOutQuint, easeOutStrong } from "../animation/easings";
|
|
7
|
+
import Animate from "morpheus";
|
|
8
|
+
import type { AnimateOptions, AnimationHandle } from "../types";
|
|
9
|
+
|
|
10
|
+
/* Draggable
|
|
11
|
+
Draggable allows you to add dragging capabilities to any element. Supports mobile devices too.
|
|
12
|
+
TODO Enable constraints
|
|
13
|
+
================================================== */
|
|
14
|
+
|
|
15
|
+
interface DragEventNames {
|
|
16
|
+
down: string;
|
|
17
|
+
up: string;
|
|
18
|
+
leave: string;
|
|
19
|
+
move: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
interface DraggableOptions {
|
|
23
|
+
enable: { x: boolean; y: boolean };
|
|
24
|
+
constraint: {
|
|
25
|
+
top: number | boolean;
|
|
26
|
+
bottom: number | boolean;
|
|
27
|
+
left: number | boolean;
|
|
28
|
+
right: number | boolean;
|
|
29
|
+
};
|
|
30
|
+
momentum_multiplier: number;
|
|
31
|
+
duration: number;
|
|
32
|
+
ease: unknown;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
interface DragData {
|
|
36
|
+
sliding: boolean;
|
|
37
|
+
direction: string | null;
|
|
38
|
+
pagex: { start: number; end: number };
|
|
39
|
+
pagey: { start: number; end: number };
|
|
40
|
+
pos: { start: { x: number; y: number }; end: { x: number; y: number } };
|
|
41
|
+
new_pos: { x: number; y: number };
|
|
42
|
+
new_pos_parent: { x: number; y: number };
|
|
43
|
+
time: { start: number; end: number };
|
|
44
|
+
touch: boolean;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export class DraggableBase {
|
|
48
|
+
declare "_el": Record<string, HTMLElement>;
|
|
49
|
+
declare "options": DraggableOptions;
|
|
50
|
+
declare "animator": AnimationHandle | null;
|
|
51
|
+
declare "dragevent": DragEventNames;
|
|
52
|
+
declare "data": DragData;
|
|
53
|
+
declare "fire": EventedInstance["fire"];
|
|
54
|
+
|
|
55
|
+
mousedrag = {
|
|
56
|
+
down: "mousedown",
|
|
57
|
+
up: "mouseup",
|
|
58
|
+
leave: "mouseleave",
|
|
59
|
+
move: "mousemove",
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
touchdrag = {
|
|
63
|
+
down: "touchstart",
|
|
64
|
+
up: "touchend",
|
|
65
|
+
leave: "touchleave",
|
|
66
|
+
move: "touchmove",
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
constructor(drag_elem: HTMLElement, options: Record<string, unknown>, move_elem?: HTMLElement) {
|
|
70
|
+
// DOM ELements
|
|
71
|
+
this._el = {
|
|
72
|
+
drag: drag_elem,
|
|
73
|
+
move: drag_elem,
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
if (move_elem) {
|
|
77
|
+
this._el.move = move_elem;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
//Options
|
|
81
|
+
this.options = {
|
|
82
|
+
enable: {
|
|
83
|
+
x: true,
|
|
84
|
+
y: true,
|
|
85
|
+
},
|
|
86
|
+
constraint: {
|
|
87
|
+
top: false,
|
|
88
|
+
bottom: false,
|
|
89
|
+
left: false,
|
|
90
|
+
right: false,
|
|
91
|
+
},
|
|
92
|
+
momentum_multiplier: 2000,
|
|
93
|
+
duration: 1000,
|
|
94
|
+
ease: easeInOutQuint,
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
// Animation Object
|
|
98
|
+
this.animator = null;
|
|
99
|
+
|
|
100
|
+
// Drag Event Type
|
|
101
|
+
this.dragevent = this.mousedrag;
|
|
102
|
+
|
|
103
|
+
if (touch) {
|
|
104
|
+
this.dragevent = this.touchdrag;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Draggable Data
|
|
108
|
+
this.data = {
|
|
109
|
+
sliding: false,
|
|
110
|
+
direction: "none",
|
|
111
|
+
pagex: {
|
|
112
|
+
start: 0,
|
|
113
|
+
end: 0,
|
|
114
|
+
},
|
|
115
|
+
pagey: {
|
|
116
|
+
start: 0,
|
|
117
|
+
end: 0,
|
|
118
|
+
},
|
|
119
|
+
pos: {
|
|
120
|
+
start: {
|
|
121
|
+
x: 0,
|
|
122
|
+
y: 0,
|
|
123
|
+
},
|
|
124
|
+
end: {
|
|
125
|
+
x: 0,
|
|
126
|
+
y: 0,
|
|
127
|
+
},
|
|
128
|
+
},
|
|
129
|
+
new_pos: {
|
|
130
|
+
x: 0,
|
|
131
|
+
y: 0,
|
|
132
|
+
},
|
|
133
|
+
new_pos_parent: {
|
|
134
|
+
x: 0,
|
|
135
|
+
y: 0,
|
|
136
|
+
},
|
|
137
|
+
time: {
|
|
138
|
+
start: 0,
|
|
139
|
+
end: 0,
|
|
140
|
+
},
|
|
141
|
+
touch: false,
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
// Merge Data and Options
|
|
145
|
+
mergeData(this.options, options);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
enable(e?: LegacyEvent): void {
|
|
149
|
+
// Temporarily disableing this until I have time to fix some issues.
|
|
150
|
+
//DomEvent.addListener(this._el.drag, this.dragevent.down, this._onDragStart, this);
|
|
151
|
+
//DomEvent.addListener(this._el.drag, this.dragevent.up, this._onDragEnd, this);
|
|
152
|
+
|
|
153
|
+
this.data.pos.start = 0 as unknown as { x: number; y: number }; //Dom.getPosition(this._el.move);
|
|
154
|
+
this._el.move.style.left = this.data.pos.start.x + "px";
|
|
155
|
+
this._el.move.style.top = this.data.pos.start.y + "px";
|
|
156
|
+
this._el.move.style.position = "absolute";
|
|
157
|
+
//this._el.move.style.zIndex = "11";
|
|
158
|
+
//this._el.move.style.cursor = "move";
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
disable() {
|
|
162
|
+
DomEvent.removeListener(this._el.drag, this.dragevent.down, this._onDragStart, this);
|
|
163
|
+
DomEvent.removeListener(this._el.drag, this.dragevent.up, this._onDragEnd, this);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
stopMomentum() {
|
|
167
|
+
if (this.animator) {
|
|
168
|
+
this.animator.stop();
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
updateConstraint(c: DraggableOptions["constraint"]): void {
|
|
173
|
+
this.options.constraint = c;
|
|
174
|
+
|
|
175
|
+
// Temporary until issues are fixed
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/* Private Methods
|
|
179
|
+
================================================== */
|
|
180
|
+
_onDragStart(e: LegacyEvent) {
|
|
181
|
+
if (touch) {
|
|
182
|
+
if (e.originalEvent) {
|
|
183
|
+
this.data.pagex.start = e.originalEvent.touches[0].screenX;
|
|
184
|
+
this.data.pagey.start = e.originalEvent.touches[0].screenY;
|
|
185
|
+
} else {
|
|
186
|
+
this.data.pagex.start = e.targetTouches[0].screenX;
|
|
187
|
+
this.data.pagey.start = e.targetTouches[0].screenY;
|
|
188
|
+
}
|
|
189
|
+
} else {
|
|
190
|
+
this.data.pagex.start = e.pageX;
|
|
191
|
+
this.data.pagey.start = e.pageY;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Center element to finger or mouse
|
|
195
|
+
if (this.options.enable.x) {
|
|
196
|
+
this._el.move.style.left = this.data.pagex.start - this._el.move.offsetWidth / 2 + "px";
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if (this.options.enable.y) {
|
|
200
|
+
this._el.move.style.top = this.data.pagey.start - this._el.move.offsetHeight / 2 + "px";
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
this.data.pos.start = Dom.getPosition(this._el.drag);
|
|
204
|
+
this.data.time.start = Date.now();
|
|
205
|
+
|
|
206
|
+
this.fire("dragstart", this.data);
|
|
207
|
+
DomEvent.addListener(this._el.drag, this.dragevent.move, this._onDragMove, this);
|
|
208
|
+
DomEvent.addListener(this._el.drag, this.dragevent.leave, this._onDragEnd, this);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
_onDragEnd(e: LegacyEvent) {
|
|
212
|
+
this.data.sliding = false;
|
|
213
|
+
DomEvent.removeListener(this._el.drag, this.dragevent.move, this._onDragMove, this);
|
|
214
|
+
DomEvent.removeListener(this._el.drag, this.dragevent.leave, this._onDragEnd, this);
|
|
215
|
+
this.fire("dragend", this.data);
|
|
216
|
+
|
|
217
|
+
// momentum
|
|
218
|
+
this._momentum();
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
_onDragMove(e: LegacyEvent) {
|
|
222
|
+
e.preventDefault();
|
|
223
|
+
this.data.sliding = true;
|
|
224
|
+
|
|
225
|
+
if (touch) {
|
|
226
|
+
if (e.originalEvent) {
|
|
227
|
+
this.data.pagex.end = e.originalEvent.touches[0].screenX;
|
|
228
|
+
this.data.pagey.end = e.originalEvent.touches[0].screenY;
|
|
229
|
+
} else {
|
|
230
|
+
this.data.pagex.end = e.targetTouches[0].screenX;
|
|
231
|
+
this.data.pagey.end = e.targetTouches[0].screenY;
|
|
232
|
+
}
|
|
233
|
+
} else {
|
|
234
|
+
this.data.pagex.end = e.pageX;
|
|
235
|
+
this.data.pagey.end = e.pageY;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
this.data.pos.end = Dom.getPosition(this._el.drag);
|
|
239
|
+
this.data.new_pos.x = -(
|
|
240
|
+
this.data.pagex.start -
|
|
241
|
+
this.data.pagex.end -
|
|
242
|
+
this.data.pos.start.x
|
|
243
|
+
); //-(this.data.pagex.start - this.data.pagex.end - this.data.pos.end.x);
|
|
244
|
+
this.data.new_pos.y = -(
|
|
245
|
+
this.data.pagey.start -
|
|
246
|
+
this.data.pagey.end -
|
|
247
|
+
this.data.pos.start.y
|
|
248
|
+
);
|
|
249
|
+
|
|
250
|
+
if (this.options.enable.x) {
|
|
251
|
+
this._el.move.style.left = this.data.new_pos.x + "px";
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
if (this.options.enable.y) {
|
|
255
|
+
this._el.move.style.top = this.data.new_pos.y + "px";
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
this.fire("dragmove", this.data);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
_momentum() {
|
|
262
|
+
const pos_adjust = {
|
|
263
|
+
x: 0,
|
|
264
|
+
y: 0,
|
|
265
|
+
time: 0,
|
|
266
|
+
},
|
|
267
|
+
pos_change = {
|
|
268
|
+
x: 0,
|
|
269
|
+
y: 0,
|
|
270
|
+
time: 0,
|
|
271
|
+
};
|
|
272
|
+
let swipe = false;
|
|
273
|
+
const _swipe_direction = "";
|
|
274
|
+
|
|
275
|
+
if (touch) {
|
|
276
|
+
//this.options.momentum_multiplier = this.options.momentum_multiplier * 2;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
pos_adjust.time = (Date.now() - this.data.time.start) * 10;
|
|
280
|
+
pos_change.time = (Date.now() - this.data.time.start) * 10;
|
|
281
|
+
|
|
282
|
+
pos_change.x =
|
|
283
|
+
this.options.momentum_multiplier *
|
|
284
|
+
(Math.abs(this.data.pagex.end) - Math.abs(this.data.pagex.start));
|
|
285
|
+
pos_change.y =
|
|
286
|
+
this.options.momentum_multiplier *
|
|
287
|
+
(Math.abs(this.data.pagey.end) - Math.abs(this.data.pagey.start));
|
|
288
|
+
|
|
289
|
+
pos_adjust.x = Math.round(pos_change.x / pos_change.time);
|
|
290
|
+
pos_adjust.y = Math.round(pos_change.y / pos_change.time);
|
|
291
|
+
|
|
292
|
+
this.data.new_pos.x = this.data.pos.end.x + pos_adjust.x;
|
|
293
|
+
this.data.new_pos.y = this.data.pos.end.y + pos_adjust.y;
|
|
294
|
+
|
|
295
|
+
if (!this.options.enable.x) {
|
|
296
|
+
this.data.new_pos.x = this.data.pos.start.x;
|
|
297
|
+
} else if (this.data.new_pos.x < 0) {
|
|
298
|
+
this.data.new_pos.x = 0;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
if (!this.options.enable.y) {
|
|
302
|
+
this.data.new_pos.y = this.data.pos.start.y;
|
|
303
|
+
} else if (this.data.new_pos.y < 0) {
|
|
304
|
+
this.data.new_pos.y = 0;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// Detect Swipe
|
|
308
|
+
if (pos_change.time < 3000) {
|
|
309
|
+
swipe = true;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// Detect Direction
|
|
313
|
+
if (Math.abs(pos_change.x) > 10000) {
|
|
314
|
+
this.data.direction = "left";
|
|
315
|
+
if (pos_change.x > 0) {
|
|
316
|
+
this.data.direction = "right";
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
// Detect Swipe
|
|
320
|
+
if (Math.abs(pos_change.y) > 10000) {
|
|
321
|
+
this.data.direction = "up";
|
|
322
|
+
if (pos_change.y > 0) {
|
|
323
|
+
this.data.direction = "down";
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
this._animateMomentum();
|
|
327
|
+
if (swipe) {
|
|
328
|
+
this.fire("swipe_" + this.data.direction, this.data);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
_animateMomentum() {
|
|
333
|
+
const pos = {
|
|
334
|
+
x: this.data.new_pos.x,
|
|
335
|
+
y: this.data.new_pos.y,
|
|
336
|
+
},
|
|
337
|
+
animate: AnimateOptions = {
|
|
338
|
+
duration: this.options.duration,
|
|
339
|
+
easing: easeOutStrong,
|
|
340
|
+
};
|
|
341
|
+
|
|
342
|
+
if (this.options.enable.y) {
|
|
343
|
+
if (this.options.constraint.top || this.options.constraint.bottom) {
|
|
344
|
+
if (pos.y > (this.options.constraint.bottom as number)) {
|
|
345
|
+
pos.y = this.options.constraint.bottom as number;
|
|
346
|
+
} else if (pos.y < (this.options.constraint.top as number)) {
|
|
347
|
+
pos.y = this.options.constraint.top as number;
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
animate.top = Math.floor(pos.y) + "px";
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
if (this.options.enable.x) {
|
|
354
|
+
if (this.options.constraint.left || this.options.constraint.right) {
|
|
355
|
+
if (pos.x > (this.options.constraint.left as number)) {
|
|
356
|
+
pos.x = this.options.constraint.left as number;
|
|
357
|
+
} else if (pos.x < (this.options.constraint.right as number)) {
|
|
358
|
+
pos.x = this.options.constraint.right as number;
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
animate.left = Math.floor(pos.x) + "px";
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
this.animator = Animate(this._el.move, animate);
|
|
365
|
+
|
|
366
|
+
this.fire("momentum", this.data);
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
export class Draggable extends Evented(DraggableBase) {
|
|
371
|
+
constructor(...args: ConstructorParameters<typeof DraggableBase>) {
|
|
372
|
+
super(...args);
|
|
373
|
+
}
|
|
374
|
+
}
|