├── .env
├── .github
└── workflows
│ └── ci.yml
├── .gitignore
├── .vscode
└── settings.json
├── LICENSE
├── README.md
├── assets
└── css
│ └── demo.css
├── deno.json
├── mod.ts
├── plugins.json
├── scripts
└── buildNpm.ts
└── site
├── components
├── BaseLayout.json
├── Link.json
├── MainFooter.json
├── MainNavigation.json
├── Markdown.json
├── MetaFields.json
├── Navigation.json
└── Scripts.json
├── dataSources.ts
├── layouts
└── siteIndex.json
├── meta.json
├── pageUtilities.ts
├── routes.json
├── scripts
└── demo.ts
├── transforms
└── markdown.ts
└── twindSetup.ts
/.env:
--------------------------------------------------------------------------------
1 | GUSTWIND_VERSION=0.39.12
2 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: gh-pages
2 |
3 | on:
4 | push:
5 | branches:
6 | - master
7 | tags:
8 | - "*"
9 |
10 | jobs:
11 | build-and-deploy:
12 | runs-on: ubuntu-latest
13 | steps:
14 | - uses: actions/checkout@v2
15 |
16 | - name: Setup deno
17 | uses: denolib/setup-deno@v2
18 | with:
19 | deno-version: v1.37.2
20 |
21 | - name: Build gh-pages
22 | run: |
23 | deno --version
24 | deno task build
25 |
26 | - name: Deploy gh-pages
27 | uses: peaceiris/actions-gh-pages@v3
28 | with:
29 | github_token: ${{ secrets.GITHUB_TOKEN }}
30 | publish_dir: ./public
31 |
32 | - name: Get tag version
33 | if: startsWith(github.ref, 'refs/tags/')
34 | id: get_tag_version
35 | run: echo ::set-output name=TAG_VERSION::${GITHUB_REF/refs\/tags\//}
36 |
37 | - uses: actions/setup-node@v2
38 | with:
39 | node-version: "18.x"
40 | registry-url: "https://registry.npmjs.org"
41 |
42 | - name: npm build
43 | run: deno run -A ./scripts/buildNpm.ts ${{steps.get_tag_version.outputs.TAG_VERSION}}
44 |
45 | - name: npm publish
46 | if: startsWith(github.ref, 'refs/tags/')
47 | env:
48 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
49 | run: |
50 | cd npm
51 | npm publish
52 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | npm
2 | public
3 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "deno.enable": true,
3 | "deno.unstable": true,
4 | "deno.lint": true,
5 | "[json]": {
6 | "editor.defaultFormatter": "denoland.vscode-deno",
7 | },
8 | "[md]": {
9 | "editor.defaultFormatter": "esbenp.prettier-vscode",
10 | },
11 | "[typescript]": {
12 | "editor.defaultFormatter": "denoland.vscode-deno",
13 | },
14 | "[typescriptreact]": {
15 | "editor.defaultFormatter": "denoland.vscode-deno",
16 | },
17 | "deno.import_intellisense_origins": {
18 | "https://deno.land": true
19 | },
20 | "deno.suggest.imports.hosts": {
21 | "https://unpkg.com": false,
22 | "https://esm.sh": false,
23 | "https://deno.land": false
24 | }
25 | }
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2012 Juho Vepsäläinen
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining
4 | a copy of this software and associated documentation files (the
5 | "Software"), to deal in the Software without restriction, including
6 | without limitation the rights to use, copy, modify, merge, publish,
7 | distribute, sublicense, and/or sell copies of the Software, and to
8 | permit persons to whom the Software is furnished to do so, subject to
9 | the following conditions:
10 |
11 | The above copyright notice and this permission notice shall be
12 | included in all copies or substantial portions of the Software.
13 |
14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | **dragjs** has been designed to make it easy to create JavaScript based drag interactions. This includes use cases such as draggable panels and different types of sliders. The idea is that you use the logic from this package to build your own components as it captures concerns such as mouse and touch handling.
2 |
3 | ## Demonstrations
4 |
5 |
6 |
7 | ### Simple draggable
8 |
9 |
12 |
13 |
14 | ```typescript
15 | import { draggable } from "dragjs";
16 |
17 | const draggableElement = document.getElementById("draggable");
18 |
19 | draggableElement && draggable({ element: draggableElement });
20 | ```
21 |
22 | ### Draggable with a specific handle
23 |
24 |
25 |
26 |
Header
27 |
Body
28 |
29 |
30 |
31 |
32 | ```typescript
33 | import { draggable } from "dragjs";
34 |
35 | const element = document.getElementById("draggabletwo");
36 | const handle = element.children[0];
37 |
38 | element && handle && draggable({ element, handle });
39 | ```
40 |
41 | ### 1D slider
42 |
43 |
44 |
45 | ```typescript
46 | import { slider } from "dragjs";
47 |
48 | const onedContainer = document.getElementById("onedContainer");
49 |
50 | onedContainer && slider({
51 | parent: onedContainer,
52 | "class": "oned",
53 | cbs: {
54 | begin: () => {
55 | log("2dslider: begin");
56 | },
57 | change: ({ x, pointer }) => {
58 | const newX = clamp(x * 100, 0, 100).toFixed(2) + "%";
59 |
60 | console.log("2dslider: " + newX);
61 |
62 | if (pointer) {
63 | pointer.style.left = newX;
64 | }
65 | },
66 | end: () => {
67 | log("2dslider: end");
68 | },
69 | },
70 | });
71 | ```
72 |
73 | ### 2D slider
74 |
75 |
76 |
77 | ```typescript
78 | import { xyslider } from "dragjs";
79 |
80 | const twodContainer = document.getElementById("twodContainer");
81 |
82 | twodContainer && xyslider({
83 | parent: twodContainer,
84 | "class": "twod",
85 | cbs: {
86 | change: ({ x, y, pointer }) => {
87 | const newX = clamp(x * 100, 0, 100).toFixed(2) + "%";
88 | const newY = clamp(y * 100, 0, 100).toFixed(2) + "%";
89 |
90 | console.log("x: " + newX + ", y: " + newY);
91 |
92 | if (pointer) {
93 | pointer.style.left = newX;
94 | pointer.style.top = newY;
95 | }
96 | },
97 | },
98 | });
99 | ```
100 |
101 | ## Contributors
102 |
103 | * [Jean Carrière](https://github.com/JeanCarriere)
104 | * [Sam Potts](https://github.com/SamPotts) - Use getBoundingClientRect(), IE9+ support
105 |
106 | ## Development
107 |
108 | Run the available commands through `deno task`.
109 |
110 | To publish, tag a release with the desired version (i.e. `git tag 0.13.0`) and then `git push`.
111 |
112 | ## License
113 |
114 | **dragjs** is available under MIT. See LICENSE for more details.
115 |
--------------------------------------------------------------------------------
/assets/css/demo.css:
--------------------------------------------------------------------------------
1 | #value {
2 | position: fixed;
3 | right: 0;
4 | bottom: 0;
5 | margin-right: 1em;
6 | margin-bottom: 1em;
7 | }
8 |
9 | #draggableParent {
10 | position: relative;
11 | }
12 |
13 | #draggable {
14 | right: 0;
15 | position: absolute;
16 | background-color: #ada;
17 | border: 1px solid black;
18 | width: 100px;
19 | height: 100px;
20 | text-align: center;
21 | cursor: move;
22 | }
23 |
24 | #draggabletwo {
25 | right: 0;
26 | position: absolute;
27 | background-color: #ada;
28 | border: 1px solid black;
29 | width: 100px;
30 | height: 100px;
31 | text-align: center;
32 | cursor: move;
33 | }
34 |
35 | #twodContainer {
36 | top: 50px;
37 | left: 50px;
38 | overflow: scroll;
39 | width: 150px;
40 | height: 150px;
41 | }
42 |
43 | .twod {
44 | background-color: #aad;
45 | border: 1px solid black;
46 | width: 200px;
47 | height: 200px;
48 | cursor: move;
49 | }
50 |
51 | .twod .pointer {
52 | position: relative;
53 | z-index: 2;
54 | margin-left: -7px;
55 | margin-top: -7px;
56 | width: 10px;
57 | height: 10px;
58 | border: 2px solid black;
59 | -mox-border-radius: 5px;
60 | border-radius: 5px;
61 | }
62 |
63 | .oned {
64 | background-color: #daa;
65 | border: 1px solid black;
66 | width: 300px;
67 | height: 30px;
68 | cursor: move;
69 | }
70 |
71 | .oned .pointer {
72 | position: relative;
73 | background-color: black;
74 | z-index: 2;
75 | margin-left: -2px;
76 | width: 4px;
77 | height: 30px;
78 | }
79 |
--------------------------------------------------------------------------------
/deno.json:
--------------------------------------------------------------------------------
1 | {
2 | "tasks": {
3 | "build": "deno run -A --unstable --no-check https://deno.land/x/gustwind@v0.39.12/gustwind-cli/mod.ts -b -t cpuHalf -o ./public",
4 | "build:linked": "gustwind -b -t cpuHalf -o ./public",
5 | "start": "deno run -A --unstable --no-check https://deno.land/x/gustwind@v0.39.12/gustwind-cli/mod.ts -d -p 3000",
6 | "start:linked": "gustwind -d -p 3000",
7 | "serve": "deno run -A --unstable --no-check https://deno.land/x/gustwind@v0.39.12/gustwind-cli/mod.ts -s -p 3000 -i ./public",
8 | "serve:linked": "gustwind -s -p 3000 -i ./public"
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/mod.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | type Callback = (
4 | { x, y, cursor, elem, pointer }: {
5 | x: number;
6 | y: number;
7 | cursor: Coordinate;
8 | elem: HTMLElement;
9 | e: MouseEvent | TouchEvent;
10 | pointer?: HTMLElement;
11 | },
12 | ) => boolean | void;
13 | type Callbacks = {
14 | begin?: Callback;
15 | change?: Callback;
16 | end?: Callback;
17 | };
18 | type Position = "left" | "right";
19 |
20 | function draggable(
21 | { element, handle, xPosition }: {
22 | element: HTMLElement;
23 | handle?: HTMLElement;
24 | xPosition?: Position;
25 | },
26 | cbs?: Callbacks,
27 | ) {
28 | if (!element) {
29 | console.warn("drag is missing elem!");
30 | return;
31 | }
32 |
33 | dragTemplate(
34 | element,
35 | "touchstart",
36 | "touchmove",
37 | "touchend",
38 | cbs,
39 | handle,
40 | xPosition,
41 | );
42 | dragTemplate(
43 | element,
44 | "mousedown",
45 | "mousemove",
46 | "mouseup",
47 | cbs,
48 | handle,
49 | xPosition,
50 | );
51 | }
52 |
53 | function xyslider(o: { parent: HTMLElement; class: string; cbs: Callbacks }) {
54 | const twod = div(o["class"] || "", o.parent);
55 | const pointer = div("pointer", twod);
56 |
57 | div("shape shape1", pointer);
58 | div("shape shape2", pointer);
59 | div("bg bg1", twod);
60 | div("bg bg2", twod);
61 |
62 | draggable({ element: twod }, attachPointer(o.cbs, pointer));
63 |
64 | return {
65 | background: twod,
66 | pointer,
67 | };
68 | }
69 |
70 | function slider(o: { parent: HTMLElement; class: string; cbs: Callbacks }) {
71 | const oned = div(o["class"], o.parent);
72 | const pointer = div("pointer", oned);
73 |
74 | div("shape", pointer);
75 | div("bg", oned);
76 |
77 | draggable({ element: oned }, attachPointer(o.cbs, pointer));
78 |
79 | return {
80 | background: oned,
81 | pointer: pointer,
82 | };
83 | }
84 |
85 | function attachPointer(cbs: Callbacks, pointer: HTMLElement): Callbacks {
86 | const ret: Record = {};
87 |
88 | Object.entries(cbs).forEach(([name, callback]) => {
89 | ret[name] = (p) => {
90 | callback({ ...p, pointer });
91 | };
92 | });
93 |
94 | return ret as Callbacks;
95 | }
96 |
97 | // move to elemutils lib?
98 | function div(klass: string, p: HTMLElement) {
99 | return e("div", klass, p);
100 | }
101 |
102 | function e(type: string, klass: string, p: HTMLElement) {
103 | const elem = document.createElement(type);
104 |
105 | if (klass) {
106 | elem.className = klass;
107 | }
108 | p.appendChild(elem);
109 |
110 | return elem;
111 | }
112 |
113 | function dragTemplate(
114 | elem: HTMLElement,
115 | down: string,
116 | move: string,
117 | up: string,
118 | cbs?: Callbacks,
119 | handle?: HTMLElement,
120 | xPosition?: Position,
121 | ) {
122 | cbs = getCbs(cbs, xPosition);
123 |
124 | const beginCb = cbs.begin;
125 | const changeCb = cbs.change;
126 | const endCb = cbs.end;
127 |
128 | on(handle || elem, down, (e) => {
129 | // @ts-ignore Figure out how to type this
130 | const moveHandler = (e: Event) => callCb(changeCb, elem, e);
131 |
132 | function upHandler() {
133 | off(document, move, moveHandler);
134 | off(document, up, upHandler);
135 |
136 | // @ts-ignore Figure out how to type this
137 | callCb(endCb, elem, e);
138 | }
139 |
140 | on(document, move, moveHandler);
141 | on(document, up, upHandler);
142 |
143 | // @ts-ignore Figure out how to type this
144 | callCb(beginCb, elem, e);
145 | });
146 | }
147 |
148 | // https://github.com/WICG/EventListenerOptions/blob/gh-pages/explainer.md#feature-detection
149 | function on(
150 | elem: HTMLElement | Document,
151 | evt: string,
152 | handler: (e: Event) => void,
153 | ) {
154 | // Test via a getter in the options object to see if the passive property is accessed
155 | let supportsPassive = false;
156 | try {
157 | const opts = Object.defineProperty({}, "passive", {
158 | get: function () {
159 | supportsPassive = true;
160 |
161 | return undefined;
162 | },
163 | });
164 |
165 | // @ts-ignore Deno doesn't know this
166 | // deno-shim-ignore
167 | globalThis.addEventListener("testPassive", null, opts);
168 |
169 | // @ts-ignore Deno doesn't know this
170 | // deno-shim-ignore
171 | globalThis.removeEventListener("testPassive", null, opts);
172 | } catch (err) {
173 | console.error(err);
174 | }
175 |
176 | elem.addEventListener(
177 | evt,
178 | handler,
179 | supportsPassive ? { passive: false } : false,
180 | );
181 | }
182 |
183 | function off(
184 | elem: HTMLElement | Document,
185 | evt: string,
186 | handler: (event: Event) => void,
187 | ) {
188 | elem.removeEventListener(evt, handler, false);
189 | }
190 |
191 | type Coordinate = { x: number; y: number };
192 |
193 | function getCbs(
194 | cbs?: Callbacks,
195 | xPosition: Position = "left",
196 | ): Callbacks {
197 | let initialOffset: Coordinate;
198 | let initialPos: Coordinate;
199 |
200 | const defaultCbs: Callbacks = {
201 | begin: (c) => {
202 | const bodyWidth = document.body.clientWidth;
203 |
204 | initialOffset = {
205 | x: xPosition === "left"
206 | ? c.elem.offsetLeft
207 | : bodyWidth - c.elem.offsetLeft - c.elem.clientWidth,
208 | y: c.elem.offsetTop,
209 | };
210 | initialPos = xPosition === "left"
211 | ? c.cursor
212 | : { x: bodyWidth - c.cursor.x, y: c.cursor.y };
213 | },
214 | change: (c) => {
215 | if (
216 | typeof initialOffset.x !== "number" || typeof c.cursor.x !== "number" ||
217 | typeof initialPos.x !== "number"
218 | ) {
219 | return;
220 | }
221 |
222 | const bodyWidth = document.body.clientWidth;
223 |
224 | style(
225 | c.elem,
226 | xPosition,
227 | xPosition === "left"
228 | ? (initialOffset.x + c.cursor.x - initialPos.x) + "px"
229 | : (initialOffset.x + (bodyWidth - c.cursor.x) - initialPos.x) + "px",
230 | );
231 |
232 | if (
233 | typeof initialOffset.y !== "number" || typeof c.cursor.y !== "number" ||
234 | typeof initialPos.y !== "number"
235 | ) {
236 | return;
237 | }
238 |
239 | style(
240 | c.elem,
241 | "top",
242 | (initialOffset.y + c.cursor.y - initialPos.y) + "px",
243 | );
244 | },
245 | end: () => {},
246 | };
247 |
248 | if (cbs) {
249 | return {
250 | begin: cbs.begin
251 | ? ((args) => {
252 | const ret = cbs.begin && cbs.begin(args);
253 |
254 | if (ret) {
255 | // @ts-ignore This is fine
256 | return defaultCbs.begin(args);
257 | }
258 | })
259 | : defaultCbs.begin,
260 | change: cbs.change
261 | ? ((args) => {
262 | const ret = cbs.change && cbs.change(args);
263 |
264 | if (ret) {
265 | // @ts-ignore This is fine
266 | return defaultCbs.change(args);
267 | }
268 | })
269 | : defaultCbs.change,
270 | end: cbs.end || defaultCbs.end,
271 | };
272 | }
273 |
274 | return defaultCbs;
275 | }
276 |
277 | function style(e: HTMLElement, prop: string, value: string) {
278 | // @ts-ignore https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/style#setting_styles
279 | e.style[prop] = value;
280 | }
281 |
282 | function callCb(
283 | cb: (
284 | { x, y, cursor, elem, e }: {
285 | x: number;
286 | y: number;
287 | cursor: Coordinate;
288 | elem: HTMLElement;
289 | e: Event;
290 | },
291 | ) => void,
292 | elem: HTMLElement,
293 | e: MouseEvent | TouchEvent,
294 | ) {
295 | e.preventDefault();
296 |
297 | // http://www.quirksmode.org/js/findpos.html
298 | const offset = elem.getBoundingClientRect();
299 | const width = elem.clientWidth;
300 | const height = elem.clientHeight;
301 | const cursor = {
302 | x: cursorX(elem, e),
303 | y: cursorY(elem, e),
304 | };
305 |
306 | if (typeof cursor.x !== "number" || typeof cursor.y !== "number") {
307 | return;
308 | }
309 |
310 | const x = (cursor.x - offset.left) / width;
311 | const y = (cursor.y - offset.top) / height;
312 |
313 | cb({
314 | x: isNaN(x) ? 0 : x,
315 | y: isNaN(y) ? 0 : y,
316 | cursor: cursor as Coordinate,
317 | elem,
318 | e,
319 | });
320 | }
321 |
322 | // http://javascript.about.com/library/blmousepos.htm
323 | function cursorX(_elem: HTMLElement, evt: MouseEvent | TouchEvent) {
324 | // https://github.com/bebraw/dragjs/issues/8
325 | if (window.TouchEvent && evt instanceof window.TouchEvent) {
326 | return evt.touches.item(0)?.clientX;
327 | }
328 |
329 | return (evt as MouseEvent).clientX;
330 | }
331 | function cursorY(_elem: HTMLElement, evt: MouseEvent | TouchEvent) {
332 | // https://github.com/bebraw/dragjs/issues/8
333 | if (window.TouchEvent && evt instanceof window.TouchEvent) {
334 | return evt.touches.item(0)?.clientY;
335 | }
336 |
337 | return (evt as MouseEvent).clientY;
338 | }
339 |
340 | export { draggable, slider, xyslider };
341 |
--------------------------------------------------------------------------------
/plugins.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "path": "https://deno.land/x/gustwind@v${GUSTWIND_VERSION}/plugins/breezewind-router/mod.ts",
4 | "options": {
5 | "dataSourcesPath": "./site/dataSources.ts",
6 | "routesPath": "./site/routes.json"
7 | }
8 | },
9 | {
10 | "path": "https://deno.land/x/gustwind@v${GUSTWIND_VERSION}/plugins/breezewind-renderer/mod.ts",
11 | "options": {
12 | "componentsPath": "./site/components",
13 | "metaPath": "./site/meta.json",
14 | "layoutsPath": "./site/layouts",
15 | "pageUtilitiesPath": "./site/pageUtilities.ts"
16 | }
17 | },
18 | {
19 | "path": "https://deno.land/x/gustwind@v${GUSTWIND_VERSION}/plugins/copy/mod.ts",
20 | "options": {
21 | "inputPath": "./assets",
22 | "outputPath": "./assets"
23 | }
24 | },
25 | {
26 | "path": "https://deno.land/x/gustwind@v${GUSTWIND_VERSION}/plugins/script/mod.ts",
27 | "options": {
28 | "scripts": [],
29 | "scriptsPath": ["./site/scripts"]
30 | }
31 | },
32 | {
33 | "path": "https://deno.land/x/gustwind@v${GUSTWIND_VERSION}/plugins/twind/mod.ts",
34 | "options": { "setupPath": "./site/twindSetup.ts" }
35 | }
36 | ]
37 |
--------------------------------------------------------------------------------
/scripts/buildNpm.ts:
--------------------------------------------------------------------------------
1 | // ex. scripts/build_npm.ts
2 | import { build } from "https://deno.land/x/dnt@0.7.0/mod.ts";
3 |
4 | await build({
5 | entryPoints: ["./mod.ts"],
6 | outDir: "./npm",
7 | package: {
8 | // package.json properties
9 | name: "dragjs",
10 | version: Deno.args[0],
11 | description:
12 | "Simple utility to make it easier to implement drag based things (ie. sliders and such)",
13 | license: "MIT",
14 | repository: {
15 | type: "git",
16 | url: "git+https://github.com/bebraw/dragjs.git",
17 | },
18 | bugs: {
19 | url: "https://github.com/bebraw/dragjs/issues",
20 | },
21 | keywords: ["drag", "dragging", "draggable"],
22 | },
23 | });
24 |
25 | // post build steps
26 | Deno.copyFileSync("LICENSE", "npm/LICENSE");
27 | Deno.copyFileSync("README.md", "npm/README.md");
28 |
--------------------------------------------------------------------------------
/site/components/BaseLayout.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "type": "!DOCTYPE",
4 | "attributes": {
5 | "html": ""
6 | },
7 | "closingCharacter": ""
8 | },
9 | {
10 | "type": "html",
11 | "attributes": {
12 | "lang": {
13 | "utility": "get",
14 | "parameters": ["context", "language"]
15 | }
16 | },
17 | "children": [
18 | {
19 | "type": "head",
20 | "children": [
21 | {
22 | "type": "MetaFields"
23 | }
24 | ]
25 | },
26 | {
27 | "type": "body",
28 | "children": [
29 | {
30 | "type": "MainNavigation"
31 | },
32 | {
33 | "visibleIf": [{
34 | "utility": "get",
35 | "parameters": ["props", "showToc"]
36 | }],
37 | "type": "aside",
38 | "class": "fixed top-16 pl-4 hidden lg:inline",
39 | "children": [
40 | {
41 | "type": "TableOfContents"
42 | }
43 | ]
44 | },
45 | {
46 | "type": "main",
47 | "children": {
48 | "utility": "render",
49 | "parameters": [
50 | {
51 | "utility": "get",
52 | "parameters": ["props", "content"]
53 | }
54 | ]
55 | }
56 | },
57 | {
58 | "type": "MainFooter"
59 | },
60 | {
61 | "type": "Scripts"
62 | }
63 | ]
64 | }
65 | ]
66 | }
67 | ]
68 |
--------------------------------------------------------------------------------
/site/components/Link.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "a",
3 | "class": "underline",
4 | "classList": {
5 | "font-bold": [
6 | { "utility": "get", "parameters": ["props", "href"] },
7 | {
8 | "utility": "trim",
9 | "parameters": [
10 | { "utility": "get", "parameters": ["context", "pagePath"] },
11 | "/"
12 | ]
13 | }
14 | ]
15 | },
16 | "children": {
17 | "utility": "render",
18 | "parameters": [
19 | {
20 | "utility": "get",
21 | "parameters": ["props", "children"]
22 | }
23 | ]
24 | },
25 | "attributes": {
26 | "href": {
27 | "utility": "validateUrl",
28 | "parameters": [
29 | {
30 | "utility": "get",
31 | "parameters": ["props", "href"]
32 | }
33 | ]
34 | }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/site/components/MainFooter.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "footer",
3 | "class": "py-8 font-light bg-gradient-to-br from-green-100 to-blue-200",
4 | "children": [
5 | {
6 | "type": "div",
7 | "class": "flex flex-col container mx-auto gap-4",
8 | "children": [
9 | {
10 | "type": "section",
11 | "class": "md:mx-auto px-4 sm:px-0 grid grid-rows-3 md:grid-rows-none md:grid-cols-3 gap-4 md:gap-8 w-full md:max-w-3xl",
12 | "children": [
13 | {
14 | "type": "div",
15 | "class": "flex flex-col",
16 | "children": [
17 | {
18 | "type": "h2",
19 | "children": "About"
20 | },
21 | {
22 | "type": "div",
23 | "class": "font-thin",
24 | "children": "dragjs makes it easy to implement draggables in JavaScript."
25 | }
26 | ]
27 | },
28 | {
29 | "type": "div",
30 | "class": "flex flex-col",
31 | "children": [
32 | {
33 | "type": "h2",
34 | "children": "Navigation"
35 | },
36 | {
37 | "type": "div",
38 | "class": "flex flex-col gap-2 font-thin",
39 | "children": [
40 | {
41 | "type": "Navigation"
42 | }
43 | ]
44 | }
45 | ]
46 | },
47 | {
48 | "type": "div",
49 | "class": "flex flex-col",
50 | "children": [
51 | {
52 | "type": "h2",
53 | "children": "Credits"
54 | },
55 | {
56 | "type": "Markdown",
57 | "props": {
58 | "type": "div",
59 | "class": "font-thin",
60 | "inputText": "dragjs was developed by [Juho Vepsäläinen](https://github.com/bebraw)."
61 | }
62 | }
63 | ]
64 | }
65 | ]
66 | }
67 | ]
68 | }
69 | ]
70 | }
71 |
72 |
--------------------------------------------------------------------------------
/site/components/MainNavigation.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "nav",
3 | "class": "sticky top-0 opacity-80 px-8 py-4 gap-4 flex flex-col sm:flex-row justify-between bg-gray-100 font-thin filter drop-shadow-sm",
4 | "children": [
5 | {
6 | "type": "div",
7 | "class": "flex flex-wrap gap-4 ml-auto sm:ml-0",
8 | "children": [
9 | {
10 | "type": "a",
11 | "class": "scale-150 whitespace-nowrap",
12 | "children": "✥",
13 | "attributes": {
14 | "href": "/"
15 | }
16 | },
17 | {
18 | "type": "Navigation"
19 | }
20 | ]
21 | },
22 | {
23 | "type": "div",
24 | "class": "flex gap-4 ml-auto sm:ml-0",
25 | "children": [
26 | {
27 | "type": "Link",
28 | "props": {
29 | "children": "GitHub",
30 | "href": "https://github.com/bebraw/dragjs"
31 | }
32 | }
33 | ]
34 | }
35 | ]
36 | }
37 |
38 |
--------------------------------------------------------------------------------
/site/components/Markdown.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": { "utility": "get", "parameters": ["props", "type"] },
3 | "class": { "utility": "get", "parameters": ["props", "class"] },
4 | "children": {
5 | "utility": "processMarkdown",
6 | "parameters": [{ "utility": "get", "parameters": ["props", "inputText"] }]
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/site/components/MetaFields.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "type": "link",
4 | "attributes": {
5 | "rel": "icon",
6 | "href": "data:image/svg+xml,"
7 | }
8 | },
9 | {
10 | "type": "link",
11 | "attributes": {
12 | "rel": "stylesheet",
13 | "href": "./assets/css/demo.css"
14 | }
15 | },
16 | {
17 | "_reference": "https://web.dev/defer-non-critical-css/",
18 | "type": "link",
19 | "attributes": {
20 | "rel": "preload",
21 | "href": "https://cdn.jsdelivr.net/gh/highlightjs/highlight.js@11.3.1/src/styles/github-dark.min.css",
22 | "as": "style",
23 | "onload": "this.onload=null;this.rel='stylesheet'"
24 | }
25 | },
26 | {
27 | "type": "noscript",
28 | "children": [
29 | {
30 | "type": "link",
31 | "attributes": {
32 | "rel": "stylesheet",
33 | "href": "https://cdn.jsdelivr.net/gh/highlightjs/highlight.js@11.3.1/src/styles/github-dark.min.css"
34 | }
35 | }
36 | ]
37 | },
38 | {
39 | "type": "meta",
40 | "attributes": {
41 | "name": "pagepath",
42 | "content": {
43 | "utility": "get",
44 | "parameters": ["context", "pagePath"]
45 | }
46 | }
47 | },
48 | {
49 | "type": "meta",
50 | "attributes": {
51 | "charset": "UTF-8",
52 | "name": "viewport",
53 | "content": "width=device-width, initial-scale=1.0"
54 | }
55 | },
56 | {
57 | "type": "meta",
58 | "attributes": {
59 | "name": "og:site_name",
60 | "content": {
61 | "utility": "get",
62 | "parameters": ["context", "meta.siteName"]
63 | }
64 | }
65 | },
66 | {
67 | "type": "meta",
68 | "attributes": {
69 | "name": "twitter:site",
70 | "content": {
71 | "utility": "get",
72 | "parameters": ["context", "meta.siteName"]
73 | }
74 | }
75 | },
76 | {
77 | "type": "meta",
78 | "attributes": {
79 | "name": "og:title",
80 | "content": {
81 | "utility": "get",
82 | "parameters": ["context", "meta.title"]
83 | }
84 | }
85 | },
86 | {
87 | "type": "meta",
88 | "attributes": {
89 | "name": "twitter:title",
90 | "content": {
91 | "utility": "get",
92 | "parameters": ["context", "meta.title"]
93 | }
94 | }
95 | },
96 | {
97 | "type": "meta",
98 | "attributes": {
99 | "name": "description",
100 | "content": {
101 | "utility": "get",
102 | "parameters": ["context", "meta.description"]
103 | }
104 | }
105 | },
106 | {
107 | "type": "meta",
108 | "attributes": {
109 | "name": "og:description",
110 | "content": {
111 | "utility": "get",
112 | "parameters": ["context", "meta.description"]
113 | }
114 | }
115 | },
116 | {
117 | "type": "meta",
118 | "attributes": {
119 | "name": "twitter:description",
120 | "content": {
121 | "utility": "get",
122 | "parameters": ["context", "meta.description"]
123 | }
124 | }
125 | },
126 | {
127 | "type": "meta",
128 | "attributes": {
129 | "name": "built",
130 | "content": {
131 | "utility": "get",
132 | "parameters": ["context", "meta.built"]
133 | }
134 | }
135 | },
136 | {
137 | "type": "title",
138 | "children": {
139 | "utility": "get",
140 | "parameters": ["context", "meta.title"]
141 | }
142 | }
143 | ]
144 |
--------------------------------------------------------------------------------
/site/components/Navigation.json:
--------------------------------------------------------------------------------
1 | []
2 |
--------------------------------------------------------------------------------
/site/components/Scripts.json:
--------------------------------------------------------------------------------
1 | {
2 | "foreach": [{ "utility": "get", "parameters": ["context", "scripts"] }, {
3 | "type": "script",
4 | "attributes": {
5 | "type": {
6 | "utility": "get",
7 | "parameters": ["props", "type"]
8 | },
9 | "src": {
10 | "utility": "get",
11 | "parameters": ["props", "src"]
12 | }
13 | }
14 | }]
15 | }
16 |
--------------------------------------------------------------------------------
/site/dataSources.ts:
--------------------------------------------------------------------------------
1 | import markdown from "./transforms/markdown.ts";
2 |
3 | async function processMarkdown(filename: string) {
4 | return markdown(await Deno.readTextFile(filename));
5 | }
6 |
7 | export { processMarkdown };
8 |
--------------------------------------------------------------------------------
/site/layouts/siteIndex.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "BaseLayout",
3 | "props": {
4 | "content": [
5 | {
6 | "type": "header",
7 | "class": "bg-gradient-to-br from-blue-200 to-green-100 py-8",
8 | "children": [
9 | {
10 | "type": "div",
11 | "class": "sm:mx-auto px-4 py-4 sm:py-8 max-w-3xl flex",
12 | "children": [
13 | {
14 | "type": "div",
15 | "class": "flex flex-col gap-8",
16 | "children": [
17 | {
18 | "type": "h1",
19 | "class": "text-4xl md:text-8xl",
20 | "children": [
21 | {
22 | "type": "span",
23 | "class": "whitespace-nowrap pr-4",
24 | "children": "✥"
25 | },
26 | {
27 | "type": "span",
28 | "children": "dragjs"
29 | }
30 | ]
31 | },
32 | {
33 | "type": "h2",
34 | "class": "text-xl md:text-4xl font-extralight",
35 | "children": "Easy dragging with JavaScript"
36 | }
37 | ]
38 | }
39 | ]
40 | }
41 | ]
42 | },
43 | {
44 | "type": "div",
45 | "class": "md:mx-auto my-8 px-4 md:px-0 w-full lg:max-w-3xl",
46 | "children": {
47 | "utility": "get",
48 | "parameters": ["context", "readme.content"]
49 | }
50 | }
51 | ]
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/site/meta.json:
--------------------------------------------------------------------------------
1 | {
2 | "language": "en",
3 | "siteName": "dragjs",
4 | "url": "http://bebraw.github.io/dragjs/"
5 | }
6 |
--------------------------------------------------------------------------------
/site/pageUtilities.ts:
--------------------------------------------------------------------------------
1 | import md from "./transforms/markdown.ts";
2 | import type { Context } from "https://deno.land/x/gustwind@v0.39.4/breezewind/types.ts";
3 | import type { Routes } from "https://deno.land/x/gustwind@v0.39.4/types.ts";
4 |
5 | function init({ routes }: { routes: Routes }) {
6 | async function processMarkdown(_: Context, input: string) {
7 | return (await md(input)).content;
8 | }
9 |
10 | function trim(_: Context, str: string, char: string) {
11 | if (!str) {
12 | throw new Error("No string to trim!");
13 | }
14 |
15 | // Exception for /
16 | if (str === char) {
17 | return str;
18 | }
19 |
20 | // Adapted from https://www.sitepoint.com/trimming-strings-in-javascript/
21 | return str.replace(new RegExp("^[" + char + "]+"), "").replace(
22 | new RegExp("[" + char + "]+$"),
23 | "",
24 | );
25 | }
26 |
27 | function validateUrl(_: Context, url: string) {
28 | if (!url) {
29 | return;
30 | }
31 |
32 | if (routes[url]) {
33 | return url === "/" ? "/" : `/${url}/`;
34 | }
35 |
36 | // TODO: This would be a good spot to check the url doesn't 404
37 | // To keep this fast, some kind of local, time-based cache would
38 | // be good to have to avoid hitting the urls all the time.
39 | if (url.startsWith("http")) {
40 | return url;
41 | }
42 |
43 | throw new Error(`Failed to find matching url for "${url}"`);
44 | }
45 |
46 | return { processMarkdown, trim, validateUrl };
47 | }
48 |
49 | export { init };
50 |
--------------------------------------------------------------------------------
/site/routes.json:
--------------------------------------------------------------------------------
1 | {
2 | "/": {
3 | "layout": "siteIndex",
4 | "meta": {
5 | "title": "dragjs",
6 | "description": "Simple drag utilities"
7 | },
8 | "scripts": [{ "name": "demo", "srcPrefix": "./" }],
9 | "dataSources": [
10 | {
11 | "operation": "processMarkdown",
12 | "parameters": ["./README.md"],
13 | "name": "readme"
14 | }
15 | ]
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/site/scripts/demo.ts:
--------------------------------------------------------------------------------
1 | import { draggable, slider, xyslider } from "../../mod.ts";
2 |
3 | window.onload = function () {
4 | const draggableElement = document.getElementById("draggable");
5 |
6 | draggableElement &&
7 | draggable({
8 | element: draggableElement,
9 | }, {
10 | change: ({ x, y }) => {
11 | log("draggable x:" + x.toFixed(2) + ", y: " + y.toFixed(2));
12 |
13 | return true;
14 | },
15 | });
16 |
17 | const draggableTwoElement = document.getElementById("draggabletwo");
18 | const handle = draggableTwoElement?.children[0] as HTMLElement;
19 |
20 | draggableTwoElement && handle && draggable({ element: draggableTwoElement, handle });
21 |
22 | const onedContainer = document.getElementById("onedContainer");
23 |
24 | onedContainer && slider({
25 | parent: onedContainer,
26 | "class": "oned",
27 | cbs: {
28 | begin: () => {
29 | log("2dslider: begin");
30 | },
31 | change: ({ x, pointer }) => {
32 | const newX = clamp(x * 100, 0, 100).toFixed(2) + "%";
33 |
34 | log("2dslider: " + newX);
35 |
36 | if (pointer) {
37 | pointer.style.left = newX;
38 | }
39 | },
40 | end: () => {
41 | log("2dslider: end");
42 | },
43 | },
44 | });
45 |
46 | const twodContainer = document.getElementById("twodContainer");
47 |
48 | twodContainer && xyslider({
49 | parent: twodContainer,
50 | "class": "twod",
51 | cbs: {
52 | change: ({ x, y, pointer }) => {
53 | const newX = clamp(x * 100, 0, 100).toFixed(2) + "%";
54 | const newY = clamp(y * 100, 0, 100).toFixed(2) + "%";
55 |
56 | log("x: " + newX + ", y: " + newY);
57 |
58 | if (pointer) {
59 | pointer.style.left = newX;
60 | pointer.style.top = newY;
61 | }
62 | },
63 | },
64 | });
65 | };
66 |
67 | function log(msg: string) {
68 | const valueElement = document.getElementById("value");
69 |
70 | console.log(msg);
71 |
72 | if (valueElement) {
73 | valueElement.innerHTML = msg;
74 | }
75 | }
76 |
77 | function clamp(a: number, min: number, max: number) {
78 | return Math.min(Math.max(a, min), max);
79 | }
80 |
--------------------------------------------------------------------------------
/site/transforms/markdown.ts:
--------------------------------------------------------------------------------
1 | import { marked } from "https://unpkg.com/marked@4.0.0/lib/marked.esm.js";
2 | import { install, tw } from "https://esm.sh/@twind/core@1.1.1"; // 1.1.3 doesn't work!
3 | import highlight from "https://unpkg.com/@highlightjs/cdn-assets@11.3.1/es/core.min.js";
4 | import highlightJS from "https://unpkg.com/highlight.js@11.3.1/es/languages/javascript";
5 | import highlightTS from "https://unpkg.com/highlight.js@11.3.1/es/languages/typescript";
6 | import highlightXML from "https://unpkg.com/highlight.js@11.3.1/es/languages/xml";
7 | import twindSetup from '../twindSetup.ts'
8 |
9 | install(twindSetup);
10 |
11 | highlight.registerLanguage("js", highlightJS);
12 | highlight.registerLanguage("javascript", highlightJS);
13 | highlight.registerLanguage("html", highlightXML);
14 | highlight.registerLanguage("ts", highlightTS);
15 | highlight.registerLanguage("typescript", highlightTS);
16 |
17 | marked.setOptions({
18 | gfm: true,
19 | breaks: false,
20 | pedantic: false,
21 | sanitize: false,
22 | smartLists: true,
23 | smartypants: true,
24 | highlight: (code: string, language: string) =>
25 | highlight.highlight(code, { language }).value,
26 | });
27 |
28 | function transformMarkdown(input: string) {
29 | // https://github.com/markedjs/marked/issues/545
30 | const tableOfContents: { slug: string; level: number; text: string }[] = [];
31 |
32 | // https://marked.js.org/using_pro#renderer
33 | // https://github.com/markedjs/marked/blob/master/src/Renderer.js
34 | marked.use({
35 | renderer: {
36 | code(code: string, infostring: string): string {
37 | const lang = ((infostring || "").match(/\S*/) || [])[0];
38 |
39 | // @ts-ignore How to type this?
40 | if (this.options.highlight) {
41 | // @ts-ignore How to type this?
42 | const out = this.options.highlight(code, lang);
43 |
44 | if (out != null && out !== code) {
45 | code = out;
46 | }
47 | }
48 |
49 | code = code.replace(/\n$/, "") + "\n";
50 |
51 | if (!lang) {
52 | return "" +
53 | code +
54 | "
\n";
55 | }
56 |
57 | return '' +
64 | code +
65 | "
\n";
66 | },
67 | paragraph(text: string) {
68 | return '' + text + "
";
69 | },
70 | heading(
71 | text: string,
72 | level: number,
73 | raw: string,
74 | slugger: { slug: (s: string) => string },
75 | ) {
76 | const slug = slugger.slug(raw);
77 |
78 | tableOfContents.push({ slug, level, text });
79 |
80 | const classes = {
81 | 1: "inline-block underline text-gray-900 font-extrabold leading-3 text-3xl mt-0 mb-8",
82 | 2: "inline-block underline text-gray-900 font-bold leading-4 text-xl mt-4 mb-2",
83 | 3: "inline-block underline text-gray-900 font-semibold leading-5 text-lg mt-1 mb-1",
84 | 4: "inline-block underline text-gray-900 font-medium leading-6 mt-1 mb-0.5",
85 | };
86 |
87 | return (
88 | '' +
100 | text +
101 | "" +
104 | "\n"
105 | );
106 | },
107 | link(href: string, title: string, text: string) {
108 | if (href === null) {
109 | return text;
110 | }
111 | let out = '" + text + "";
116 | return out;
117 | },
118 | list(body: string, ordered: string, start: number) {
119 | const type = ordered ? "ol" : "ul",
120 | startatt = ordered && start !== 1 ? ' start="' + start + '"' : "",
121 | klass = ordered
122 | ? "list-decimal list-outside my-2"
123 | : "list-disc list-outside my-2";
124 | return (
125 | "<" +
126 | type +
127 | startatt +
128 | ' class="' +
129 | tw(klass) +
130 | '">\n' +
131 | body +
132 | "" +
133 | type +
134 | ">\n"
135 | );
136 | },
137 | },
138 | });
139 |
140 | return { content: marked(input), tableOfContents };
141 | }
142 |
143 | export default transformMarkdown;
144 |
--------------------------------------------------------------------------------
/site/twindSetup.ts:
--------------------------------------------------------------------------------
1 | import presetAutoprefix from "https://esm.sh/@twind/preset-autoprefix@1.0.5";
2 | import presetTailwind from "https://esm.sh/@twind/preset-tailwind@1.1.1";
3 |
4 | export default {
5 | presets: [presetAutoprefix(), presetTailwind()],
6 | };
7 |
--------------------------------------------------------------------------------