@3sln/nook 0.0.0-stage → 0.2.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/LICENSE +21 -0
- package/README.md +666 -2
- package/package.json +52 -4
- package/src/bl/commands.js +27 -0
- package/src/bl/menu.js +314 -0
- package/src/capture.js +129 -0
- package/src/index.js +2308 -0
- package/src/providers/attention.js +99 -0
- package/src/providers/onboarding.js +36 -0
- package/src/ui/bounds.js +160 -0
- package/src/ui/commandPalette.js +236 -0
- package/src/ui/enumPicker.js +307 -0
- package/src/ui/feedback.js +576 -0
- package/src/ui/icons.js +46 -0
- package/src/ui/keyboardLabels.js +31 -0
- package/src/ui/localization.js +95 -0
- package/src/ui/motion.js +49 -0
- package/src/ui/styles.js +1960 -0
- package/src/ui/swipeDial.js +389 -0
- package/src/ui/tutorialGraphics.js +56 -0
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { createTranslator } from "@3sln/bab";
|
|
2
|
+
|
|
3
|
+
// Each dock owns its translator. Bindings update existing text/attributes rather
|
|
4
|
+
// than replacing controls, so changing language preserves input and focus state.
|
|
5
|
+
export default function localization(host, make) {
|
|
6
|
+
const bindings = new Set();
|
|
7
|
+
const nodes = new WeakMap();
|
|
8
|
+
const finalized = new FinalizationRegistry((binding) =>
|
|
9
|
+
bindings.delete(binding),
|
|
10
|
+
);
|
|
11
|
+
let config;
|
|
12
|
+
let translator;
|
|
13
|
+
let locale;
|
|
14
|
+
const format = (id, params, singular) => {
|
|
15
|
+
if (id === "" || id == null) return "";
|
|
16
|
+
const values = typeof params === "function" ? params() : params;
|
|
17
|
+
let value = translator(String(id), values);
|
|
18
|
+
if (singular) value = value.singular(singular);
|
|
19
|
+
return String(value);
|
|
20
|
+
};
|
|
21
|
+
const apply = (binding) => {
|
|
22
|
+
const node = binding.node.deref();
|
|
23
|
+
if (!node) {
|
|
24
|
+
bindings.delete(binding);
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
const value = format(binding.id, binding.params, binding.singular);
|
|
28
|
+
if (binding.attribute) {
|
|
29
|
+
if (node.getAttribute(binding.attribute) !== value)
|
|
30
|
+
node.setAttribute(binding.attribute, value);
|
|
31
|
+
} else if (node.textContent !== value) node.textContent = value;
|
|
32
|
+
};
|
|
33
|
+
const bind = (node, attribute, id, params, singular) => {
|
|
34
|
+
let map = nodes.get(node);
|
|
35
|
+
if (!map) {
|
|
36
|
+
map = new Map();
|
|
37
|
+
nodes.set(node, map);
|
|
38
|
+
}
|
|
39
|
+
const key = attribute || "text";
|
|
40
|
+
let binding = map.get(key);
|
|
41
|
+
if (!binding) {
|
|
42
|
+
binding = { node: new WeakRef(node), attribute };
|
|
43
|
+
map.set(key, binding);
|
|
44
|
+
bindings.add(binding);
|
|
45
|
+
finalized.register(node, binding, binding);
|
|
46
|
+
}
|
|
47
|
+
Object.assign(binding, { id, params, singular });
|
|
48
|
+
apply(binding);
|
|
49
|
+
return node;
|
|
50
|
+
};
|
|
51
|
+
const configure = (value) => {
|
|
52
|
+
config = value;
|
|
53
|
+
const options = typeof value === "function" ? {} : value || {};
|
|
54
|
+
locale =
|
|
55
|
+
options.locale ||
|
|
56
|
+
host.lang ||
|
|
57
|
+
host.ownerDocument.documentElement.lang ||
|
|
58
|
+
"en";
|
|
59
|
+
translator =
|
|
60
|
+
typeof value === "function"
|
|
61
|
+
? value
|
|
62
|
+
: createTranslator({ ...options, locale }).scope("nook");
|
|
63
|
+
locale = translator.locale || locale;
|
|
64
|
+
host.anchor?.setAttribute("lang", locale);
|
|
65
|
+
host.overlayAnchor?.setAttribute("lang", locale);
|
|
66
|
+
for (const binding of bindings) apply(binding);
|
|
67
|
+
};
|
|
68
|
+
configure();
|
|
69
|
+
return {
|
|
70
|
+
get locale() {
|
|
71
|
+
return locale;
|
|
72
|
+
},
|
|
73
|
+
configure,
|
|
74
|
+
refresh() {
|
|
75
|
+
configure(config);
|
|
76
|
+
},
|
|
77
|
+
t: format,
|
|
78
|
+
text: (node, id, params, singular) =>
|
|
79
|
+
bind(node, null, id, params, singular),
|
|
80
|
+
attr: (node, name, id, params, singular) =>
|
|
81
|
+
bind(node, name, id, params, singular),
|
|
82
|
+
make(tag, part, id) {
|
|
83
|
+
const node = make(tag, part);
|
|
84
|
+
if (id !== undefined && id !== null) bind(node, null, id);
|
|
85
|
+
return node;
|
|
86
|
+
},
|
|
87
|
+
error(id, params, singular) {
|
|
88
|
+
return Object.assign(new Error(format(id, params, singular)), {
|
|
89
|
+
messageId: id,
|
|
90
|
+
messageParams: params,
|
|
91
|
+
messageSingular: singular,
|
|
92
|
+
});
|
|
93
|
+
},
|
|
94
|
+
};
|
|
95
|
+
}
|
package/src/ui/motion.js
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
// Effects own no app state. The composition root aborts their lifetime, and a
|
|
2
|
+
// new effect cancels the old effect on that element instead of accumulating.
|
|
3
|
+
export default function motion({ window, signal, host }) {
|
|
4
|
+
const running = new Map();
|
|
5
|
+
const preference = window.matchMedia("(prefers-reduced-motion: reduce)");
|
|
6
|
+
const cancel = () => {
|
|
7
|
+
for (const animation of running.values()) animation.cancel();
|
|
8
|
+
running.clear();
|
|
9
|
+
};
|
|
10
|
+
signal.addEventListener("abort", cancel, { once: true });
|
|
11
|
+
preference.addEventListener(
|
|
12
|
+
"change",
|
|
13
|
+
(event) => {
|
|
14
|
+
if (event.matches) cancel();
|
|
15
|
+
},
|
|
16
|
+
{ signal },
|
|
17
|
+
);
|
|
18
|
+
return {
|
|
19
|
+
play(
|
|
20
|
+
element,
|
|
21
|
+
frames,
|
|
22
|
+
{
|
|
23
|
+
token = "--nook-settle-duration",
|
|
24
|
+
fallback = 240,
|
|
25
|
+
easing = "cubic-bezier(.2,.8,.2,1)",
|
|
26
|
+
} = {},
|
|
27
|
+
) {
|
|
28
|
+
running.get(element)?.cancel();
|
|
29
|
+
if (signal.aborted || preference.matches || !element.animate) return;
|
|
30
|
+
const value = window
|
|
31
|
+
.getComputedStyle(host)
|
|
32
|
+
.getPropertyValue(token)
|
|
33
|
+
.trim();
|
|
34
|
+
const parsed = parseFloat(value);
|
|
35
|
+
const duration = Number.isFinite(parsed)
|
|
36
|
+
? Math.max(0, parsed * (value.endsWith("ms") ? 1 : 1000))
|
|
37
|
+
: fallback;
|
|
38
|
+
if (!duration) return;
|
|
39
|
+
const animation = element.animate(frames, { duration, easing });
|
|
40
|
+
running.set(element, animation);
|
|
41
|
+
const release = () => {
|
|
42
|
+
if (running.get(element) === animation) running.delete(element);
|
|
43
|
+
};
|
|
44
|
+
animation.onfinish = release;
|
|
45
|
+
animation.oncancel = release;
|
|
46
|
+
return animation;
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
}
|