@vitrinka/link 0.1.1 → 0.1.2
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.md +7 -0
- package/README.md +14 -0
- package/build/dock.d.ts +61 -0
- package/build/dock.js +101 -0
- package/package.json +5 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# @vitrinka/link
|
|
2
2
|
|
|
3
|
+
## 0.1.2
|
|
4
|
+
|
|
5
|
+
- **`@vitrinka/link/dock`.** The recorder HUD's snap math, shared by the
|
|
6
|
+
web and Expo recorders: `settle` (six spots, velocity-projected flick,
|
|
7
|
+
side-edge tuck), `spotRect`, `neighbour` (arrow keys), `untuck`,
|
|
8
|
+
`parsePlace` (validated stored position).
|
|
9
|
+
|
|
3
10
|
## 0.1.1
|
|
4
11
|
|
|
5
12
|
- **Workspace hint.** `linkWorkspace(base)` reads the `<slug>` of a
|
package/README.md
CHANGED
|
@@ -29,3 +29,17 @@ A token only authenticates in the workspace it was approved into, so a
|
|
|
29
29
|
URLs as a `workspace=<slug>` preselect (never the start body), and a claim
|
|
30
30
|
approved into another workspace rejects with `LinkWorkspaceMismatch`
|
|
31
31
|
(`linked`, `expected`, a ready-to-show message) — the token is discarded.
|
|
32
|
+
|
|
33
|
+
## Dock (`@vitrinka/link/dock`)
|
|
34
|
+
|
|
35
|
+
Where a recorder HUD may rest, shared by every recorder so a throw lands the
|
|
36
|
+
same way everywhere: six spots (corners plus top and bottom centre), a
|
|
37
|
+
release projected along its velocity, and a tuck into a side-edge tab when
|
|
38
|
+
more than a third of the HUD is pushed past that edge. Pure numbers.
|
|
39
|
+
|
|
40
|
+
```ts
|
|
41
|
+
import { settle, neighbour, parsePlace, spotRect } from '@vitrinka/link/dock';
|
|
42
|
+
|
|
43
|
+
settle(releasedRect, { x: vx, y: vy }, viewport, insets); // { spot: 'tr' } | { tuck: 'right', y: 0.4 }
|
|
44
|
+
neighbour({ spot: 'br' }, 'ArrowLeft'); // { spot: 'bc' } — the keyboard alternative
|
|
45
|
+
```
|
package/build/dock.d.ts
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @vitrinka/link/dock — where a recorder HUD may rest, shared by every
|
|
3
|
+
* recorder (web, Expo, the extension's port) so a throw lands the same way
|
|
4
|
+
* everywhere.
|
|
5
|
+
*
|
|
6
|
+
* Six spots — the four corners plus top and bottom centre. A release is
|
|
7
|
+
* projected along its velocity (iOS picture-in-picture: a flick lands where
|
|
8
|
+
* it is aimed, not merely nearest) and settles on the spot closest to the
|
|
9
|
+
* projected centre. A release with more than a third of the HUD pushed past
|
|
10
|
+
* a side edge tucks it into a tab on that edge instead, at the height it was
|
|
11
|
+
* dropped. Pure numbers: no DOM, no React Native.
|
|
12
|
+
*/
|
|
13
|
+
export type Spot = 'tl' | 'tc' | 'tr' | 'bl' | 'bc' | 'br';
|
|
14
|
+
export type Side = 'left' | 'right';
|
|
15
|
+
/** Where the HUD rests: a spot, or tucked into a side edge at `y` (0 top … 1 bottom). */
|
|
16
|
+
export type Place = {
|
|
17
|
+
spot: Spot;
|
|
18
|
+
} | {
|
|
19
|
+
tuck: Side;
|
|
20
|
+
y: number;
|
|
21
|
+
};
|
|
22
|
+
export interface Size {
|
|
23
|
+
w: number;
|
|
24
|
+
h: number;
|
|
25
|
+
}
|
|
26
|
+
export interface Rect extends Size {
|
|
27
|
+
x: number;
|
|
28
|
+
y: number;
|
|
29
|
+
}
|
|
30
|
+
/** Distance from the viewport edges, safe-area insets already added. */
|
|
31
|
+
export interface Insets {
|
|
32
|
+
top: number;
|
|
33
|
+
right: number;
|
|
34
|
+
bottom: number;
|
|
35
|
+
left: number;
|
|
36
|
+
}
|
|
37
|
+
export declare const SPOTS: readonly Spot[];
|
|
38
|
+
export declare const DEFAULT_PLACE: Place;
|
|
39
|
+
/** Seconds of travel a release is projected along its velocity. */
|
|
40
|
+
export declare const FLICK_PROJECTION_S = 0.3;
|
|
41
|
+
/** Fraction of the HUD's width past a side edge that tucks it. */
|
|
42
|
+
export declare const TUCK_FRACTION: number;
|
|
43
|
+
export declare function rowOf(spot: Spot): 't' | 'b';
|
|
44
|
+
export declare function colOf(spot: Spot): 'l' | 'c' | 'r';
|
|
45
|
+
/** The rect a HUD of `size` occupies when resting at `spot`. */
|
|
46
|
+
export declare function spotRect(spot: Spot, size: Size, view: Size, inset: Insets): Rect;
|
|
47
|
+
/**
|
|
48
|
+
* Where a released HUD settles. `rect` is where it was let go (viewport
|
|
49
|
+
* px), `velocity` the pointer's px/s at release.
|
|
50
|
+
*/
|
|
51
|
+
export declare function settle(rect: Rect, velocity: {
|
|
52
|
+
x: number;
|
|
53
|
+
y: number;
|
|
54
|
+
}, view: Size, inset: Insets): Place;
|
|
55
|
+
/** The spot a tucked HUD returns to: the nearer corner on its side. */
|
|
56
|
+
export declare function untuck(place: Place): Spot;
|
|
57
|
+
export type Arrow = 'ArrowLeft' | 'ArrowRight' | 'ArrowUp' | 'ArrowDown';
|
|
58
|
+
/** The spot an arrow key moves to — the keyboard alternative to dragging. */
|
|
59
|
+
export declare function neighbour(place: Place, arrow: Arrow): Place;
|
|
60
|
+
/** A stored place, validated; anything else is the default. */
|
|
61
|
+
export declare function parsePlace(raw: unknown): Place;
|
package/build/dock.js
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @vitrinka/link/dock — where a recorder HUD may rest, shared by every
|
|
3
|
+
* recorder (web, Expo, the extension's port) so a throw lands the same way
|
|
4
|
+
* everywhere.
|
|
5
|
+
*
|
|
6
|
+
* Six spots — the four corners plus top and bottom centre. A release is
|
|
7
|
+
* projected along its velocity (iOS picture-in-picture: a flick lands where
|
|
8
|
+
* it is aimed, not merely nearest) and settles on the spot closest to the
|
|
9
|
+
* projected centre. A release with more than a third of the HUD pushed past
|
|
10
|
+
* a side edge tucks it into a tab on that edge instead, at the height it was
|
|
11
|
+
* dropped. Pure numbers: no DOM, no React Native.
|
|
12
|
+
*/
|
|
13
|
+
export const SPOTS = ['tl', 'tc', 'tr', 'bl', 'bc', 'br'];
|
|
14
|
+
export const DEFAULT_PLACE = { spot: 'br' };
|
|
15
|
+
/** Seconds of travel a release is projected along its velocity. */
|
|
16
|
+
export const FLICK_PROJECTION_S = 0.3;
|
|
17
|
+
/** Fraction of the HUD's width past a side edge that tucks it. */
|
|
18
|
+
export const TUCK_FRACTION = 1 / 3;
|
|
19
|
+
export function rowOf(spot) {
|
|
20
|
+
return spot[0];
|
|
21
|
+
}
|
|
22
|
+
export function colOf(spot) {
|
|
23
|
+
return spot[1];
|
|
24
|
+
}
|
|
25
|
+
/** The rect a HUD of `size` occupies when resting at `spot`. */
|
|
26
|
+
export function spotRect(spot, size, view, inset) {
|
|
27
|
+
const col = colOf(spot);
|
|
28
|
+
const x = col === 'l' ? inset.left : col === 'r' ? view.w - inset.right - size.w : (view.w - size.w) / 2;
|
|
29
|
+
const y = rowOf(spot) === 't' ? inset.top : view.h - inset.bottom - size.h;
|
|
30
|
+
return { x, y, w: size.w, h: size.h };
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Where a released HUD settles. `rect` is where it was let go (viewport
|
|
34
|
+
* px), `velocity` the pointer's px/s at release.
|
|
35
|
+
*/
|
|
36
|
+
export function settle(rect, velocity, view, inset) {
|
|
37
|
+
const past = Math.max(-rect.x, rect.x + rect.w - view.w);
|
|
38
|
+
if (past > rect.w * TUCK_FRACTION) {
|
|
39
|
+
const side = -rect.x > rect.x + rect.w - view.w ? 'left' : 'right';
|
|
40
|
+
return { tuck: side, y: clamp01((rect.y + rect.h / 2) / Math.max(1, view.h)) };
|
|
41
|
+
}
|
|
42
|
+
const px = rect.x + rect.w / 2 + velocity.x * FLICK_PROJECTION_S;
|
|
43
|
+
const py = rect.y + rect.h / 2 + velocity.y * FLICK_PROJECTION_S;
|
|
44
|
+
let best = 'br';
|
|
45
|
+
let bestD = Infinity;
|
|
46
|
+
for (const spot of SPOTS) {
|
|
47
|
+
const r = spotRect(spot, rect, view, inset);
|
|
48
|
+
const d = Math.hypot(r.x + r.w / 2 - px, r.y + r.h / 2 - py);
|
|
49
|
+
if (d < bestD) {
|
|
50
|
+
bestD = d;
|
|
51
|
+
best = spot;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return { spot: best };
|
|
55
|
+
}
|
|
56
|
+
/** The spot a tucked HUD returns to: the nearer corner on its side. */
|
|
57
|
+
export function untuck(place) {
|
|
58
|
+
if ('spot' in place)
|
|
59
|
+
return place.spot;
|
|
60
|
+
return `${place.y < 0.5 ? 't' : 'b'}${place.tuck === 'left' ? 'l' : 'r'}`;
|
|
61
|
+
}
|
|
62
|
+
/** The spot an arrow key moves to — the keyboard alternative to dragging. */
|
|
63
|
+
export function neighbour(place, arrow) {
|
|
64
|
+
const spot = untuck(place);
|
|
65
|
+
if (!('spot' in place))
|
|
66
|
+
return { spot };
|
|
67
|
+
const cols = ['l', 'c', 'r'];
|
|
68
|
+
let row = rowOf(spot);
|
|
69
|
+
let ci = cols.indexOf(colOf(spot));
|
|
70
|
+
if (arrow === 'ArrowLeft')
|
|
71
|
+
ci = Math.max(0, ci - 1);
|
|
72
|
+
else if (arrow === 'ArrowRight')
|
|
73
|
+
ci = Math.min(2, ci + 1);
|
|
74
|
+
else if (arrow === 'ArrowUp')
|
|
75
|
+
row = 't';
|
|
76
|
+
else
|
|
77
|
+
row = 'b';
|
|
78
|
+
return { spot: `${row}${cols[ci]}` };
|
|
79
|
+
}
|
|
80
|
+
/** A stored place, validated; anything else is the default. */
|
|
81
|
+
export function parsePlace(raw) {
|
|
82
|
+
if (typeof raw === 'string') {
|
|
83
|
+
try {
|
|
84
|
+
return parsePlace(JSON.parse(raw));
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
return DEFAULT_PLACE;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
if (raw && typeof raw === 'object') {
|
|
91
|
+
const o = raw;
|
|
92
|
+
if (typeof o.spot === 'string' && SPOTS.includes(o.spot))
|
|
93
|
+
return { spot: o.spot };
|
|
94
|
+
if ((o.tuck === 'left' || o.tuck === 'right') && typeof o.y === 'number' && Number.isFinite(o.y))
|
|
95
|
+
return { tuck: o.tuck, y: clamp01(o.y) };
|
|
96
|
+
}
|
|
97
|
+
return DEFAULT_PLACE;
|
|
98
|
+
}
|
|
99
|
+
function clamp01(n) {
|
|
100
|
+
return Math.min(1, Math.max(0, n));
|
|
101
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vitrinka/link",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "vitrinka device link — the Netflix-style code flow that mints an ingest-only recorder token for the web and Expo recorders. Zero dependencies.",
|
|
5
5
|
"license": "Elastic-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -17,6 +17,10 @@
|
|
|
17
17
|
"types": "./build/index.d.ts",
|
|
18
18
|
"default": "./build/index.js"
|
|
19
19
|
},
|
|
20
|
+
"./dock": {
|
|
21
|
+
"types": "./build/dock.d.ts",
|
|
22
|
+
"default": "./build/dock.js"
|
|
23
|
+
},
|
|
20
24
|
"./package.json": "./package.json"
|
|
21
25
|
},
|
|
22
26
|
"files": ["build", "README.md", "CHANGELOG.md"],
|