@blukavantui/community 1.1.0 → 1.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/dist/build-audit.json +127 -65
- package/dist/checkbox.js +1 -1
- package/dist/chunk-4LHIQROI.js +0 -0
- package/dist/chunk-6T677NSC.js +1 -0
- package/dist/chunk-7CEOCZ5L.js +0 -0
- package/dist/chunk-7DXFNK7C.js +1 -0
- package/dist/chunk-CNIIMGT3.js +0 -0
- package/dist/chunk-CSTCEBEK.js +1 -0
- package/dist/chunk-DRZM2AL3.js +0 -0
- package/dist/{chunk-JVE6CXCJ.js → chunk-EPPETNK6.js} +1 -1
- package/dist/chunk-EPPSBHZR.js +1 -0
- package/dist/{chunk-NTFHNL7T.js → chunk-G6OW4OZM.js} +1 -1
- package/dist/chunk-GS67ZBLI.js +1 -0
- package/dist/{chunk-6EHKOVZK.js → chunk-HVR2UDFX.js} +1 -1
- package/dist/chunk-IKYBFMHI.js +1 -0
- package/dist/chunk-KVOA55NE.js +0 -0
- package/dist/{chunk-MZBB5ROJ.js → chunk-LF2HELEZ.js} +1 -1
- package/dist/{chunk-UEOCR2C3.js → chunk-OLDA73XG.js} +2 -2
- package/dist/{chunk-IZJN3EF7.js → chunk-T45FJNEQ.js} +1 -1
- package/dist/chunk-TZSX4OTV.js +1 -0
- package/dist/{chunk-GK3INPAZ.js → chunk-V2YPIC6Z.js} +1 -1
- package/dist/chunk-V4ZQLQ7L.js +0 -0
- package/dist/chunk-VAM3FPCS.js +0 -0
- package/dist/chunk-VSTKXZYY.js +0 -0
- package/dist/chunk-WG4US4JH.js +1 -0
- package/dist/chunk-WGSTNXAQ.js +0 -0
- package/dist/chunk-WJHCVECU.js +1 -0
- package/dist/chunk-Y4AXWTAD.js +0 -0
- package/dist/chunk-YLFXSHKZ.js +0 -0
- package/dist/chunk-ZDGZGAYG.js +0 -0
- package/dist/command-palette.js +1 -1
- package/dist/component-motion.css +39 -0
- package/dist/component-motion.d.ts +45 -0
- package/dist/component-motion.js +1 -0
- package/dist/content.js +1 -1
- package/dist/core.css +8 -0
- package/dist/core.js +1 -1
- package/dist/data-motion.css +4 -0
- package/dist/data-motion.d.ts +23 -0
- package/dist/data-motion.js +1 -0
- package/dist/data.js +1 -1
- package/dist/date-picker.js +1 -1
- package/dist/date-range-picker.js +1 -1
- package/dist/date-time-picker.js +1 -1
- package/dist/drawer.js +1 -1
- package/dist/essentials.css +8 -0
- package/dist/feedback.js +1 -1
- package/dist/form-layout.js +1 -1
- package/dist/index.js +1 -1
- package/dist/inputs.js +1 -1
- package/dist/layout.js +1 -1
- package/dist/modal.js +1 -1
- package/dist/motion/action-feedback.d.ts +14 -0
- package/dist/motion/border-glow.d.ts +12 -0
- package/dist/motion/button.d.ts +11 -0
- package/dist/motion/component-settings.d.ts +51 -0
- package/dist/motion/controller.d.ts +33 -0
- package/dist/motion/effects.d.ts +30 -0
- package/dist/motion/feedback.d.ts +6 -0
- package/dist/motion/loading.d.ts +9 -0
- package/dist/motion/optional.d.ts +35 -0
- package/dist/motion/photo.d.ts +7 -0
- package/dist/motion/presence.d.ts +12 -0
- package/dist/motion/scene.d.ts +10 -0
- package/dist/motion/screen.d.ts +6 -0
- package/dist/motion/scroll-reveal.d.ts +13 -0
- package/dist/motion/settings-group.d.ts +14 -0
- package/dist/motion/stagger.d.ts +12 -0
- package/dist/motion/structure.d.ts +18 -0
- package/dist/motion/tabs.d.ts +19 -0
- package/dist/motion/tokens.d.ts +11 -0
- package/dist/motion/value.d.ts +21 -0
- package/dist/motion/view-transition.d.ts +14 -0
- package/dist/motion-enhancers.d.ts +12 -0
- package/dist/motion-enhancers.js +1 -0
- package/dist/motion-settings.d.ts +3 -0
- package/dist/motion-settings.js +1 -0
- package/dist/motion.css +1076 -0
- package/dist/motion.d.ts +20 -0
- package/dist/motion.js +1 -0
- package/dist/native/form-layout.js +1 -1
- package/dist/native-motion-components.d.ts +3 -0
- package/dist/native-motion-content.d.ts +1 -0
- package/dist/native-motion-foundations.d.ts +3 -0
- package/dist/native-motion-layout.d.ts +3 -0
- package/dist/native-motion-pilot.d.ts +34 -0
- package/dist/native-motion.d.ts +71 -0
- package/dist/notification.js +1 -1
- package/dist/pagination.js +1 -1
- package/dist/radio-button.js +1 -1
- package/dist/radio-group.js +1 -1
- package/dist/sidebar.js +1 -1
- package/dist/sizes.json +273 -151
- package/dist/skeleton.css +8 -0
- package/dist/styles.css +1130 -0
- package/dist/switch.js +1 -1
- package/dist/table.js +1 -1
- package/dist/time-picker.js +1 -1
- package/dist/tree-view.js +1 -1
- package/dist/web-motion/chip-ripple.d.ts +6 -0
- package/dist/web-motion/choice-ripple.d.ts +6 -0
- package/dist/web-motion/content.d.ts +9 -0
- package/dist/web-motion/data-rows.d.ts +12 -0
- package/dist/web-motion/fields.d.ts +6 -0
- package/dist/web-motion/navigation.d.ts +6 -0
- package/dist/web-motion/notifications.d.ts +6 -0
- package/dist/web-motion/select-ripple.d.ts +6 -0
- package/dist/web-motion/statistic.d.ts +13 -0
- package/dist/web-motion/surfaces.d.ts +10 -0
- package/dist/web-motion/tree.d.ts +6 -0
- package/docs/FORM-LAYOUT-ESSENTIALS.md +4 -4
- package/edition.json +1 -1
- package/npm-shrinkwrap.json +2 -2
- package/package.json +29 -3
- package/scripts/build-motion-css.mjs +36 -0
- package/scripts/build-motion-styles.mjs +29 -0
- package/scripts/build.mjs +5 -1
- package/scripts/motion-entries.mjs +2 -0
- package/src/component-motion.ts +101 -0
- package/src/data-motion.ts +60 -0
- package/src/form-layout/choice.ts +17 -1
- package/src/motion/action-feedback.ts +38 -0
- package/src/motion/border-glow.ts +41 -0
- package/src/motion/button.ts +56 -0
- package/src/motion/component-settings.ts +147 -0
- package/src/motion/controller.ts +191 -0
- package/src/motion/effects.ts +75 -0
- package/src/motion/feedback.ts +41 -0
- package/src/motion/loading.ts +40 -0
- package/src/motion/optional.ts +123 -0
- package/src/motion/photo.ts +19 -0
- package/src/motion/presence.ts +44 -0
- package/src/motion/scene.ts +103 -0
- package/src/motion/screen.ts +15 -0
- package/src/motion/scroll-reveal.ts +49 -0
- package/src/motion/settings-group.ts +28 -0
- package/src/motion/stagger.ts +26 -0
- package/src/motion/structure.ts +75 -0
- package/src/motion/tabs.ts +105 -0
- package/src/motion/tokens.ts +2 -0
- package/src/motion/value.ts +61 -0
- package/src/motion/view-transition.ts +57 -0
- package/src/motion-enhancers.ts +12 -0
- package/src/motion-settings.ts +3 -0
- package/src/motion.ts +20 -0
- package/src/native-motion-components.ts +76 -0
- package/src/native-motion-content.ts +10 -0
- package/src/native-motion-foundations.ts +56 -0
- package/src/native-motion-layout.ts +35 -0
- package/src/native-motion-pilot.ts +151 -0
- package/src/native-motion.ts +188 -0
- package/src/web-motion/chip-ripple.ts +31 -0
- package/src/web-motion/choice-ripple.ts +30 -0
- package/src/web-motion/content.ts +74 -0
- package/src/web-motion/data-rows.ts +106 -0
- package/src/web-motion/fields.ts +52 -0
- package/src/web-motion/navigation.ts +62 -0
- package/src/web-motion/notifications.ts +67 -0
- package/src/web-motion/select-ripple.ts +23 -0
- package/src/web-motion/statistic.ts +23 -0
- package/src/web-motion/surfaces.ts +137 -0
- package/src/web-motion/tree.ts +44 -0
- package/styles/component-motion.css +39 -0
- package/styles/data-motion.css +4 -0
- package/styles/motion-optional.css +15 -0
- package/styles/motion-presets.css +92 -0
- package/styles/motion-tokens.css +6 -0
- package/styles/motion.css +125 -0
- package/styles/skeleton.css +8 -0
- package/test/component-motion-settings.test.mjs +35 -0
- package/test/content-overlay-motion.test.mjs +36 -0
- package/test/isolation.test.mjs +1 -1
- package/test/motion.test.mjs +215 -0
- package/dist/chunk-GXHANADL.js +0 -1
- /package/dist/{chunk-3PNBQVYS.js → chunk-2BSBXDOT.js} +0 -0
- /package/dist/{chunk-H7ETPJVW.js → chunk-3PODK22R.js} +0 -0
- /package/dist/{chunk-SIU7DSOA.js → chunk-KBWB4OIF.js} +0 -0
- /package/dist/{chunk-OPMD7QNV.js → chunk-W3H7XTA7.js} +0 -0
- /package/dist/{chunk-XWXEH3SW.js → chunk-XVMJG26X.js} +0 -0
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import {build} from 'esbuild';
|
|
4
|
+
const result=await build({entryPoints:['src/web-motion/content.ts'],bundle:true,format:'esm',platform:'node',write:false});
|
|
5
|
+
const {enhanceContentMotion}=await import('data:text/javascript;base64,'+Buffer.from(result.outputFiles[0].text).toString('base64'));
|
|
6
|
+
|
|
7
|
+
function fixture(mode='full') {
|
|
8
|
+
const watchers=[],jobs=[];
|
|
9
|
+
globalThis.MutationObserver=class {constructor(fn){this.fn=fn;} observe(root,options){this.root=root;this.options=options;watchers.push(this);} disconnect(){this.off=true;}};
|
|
10
|
+
globalThis.cancelAnimationFrame=()=>{};
|
|
11
|
+
const rect={left:0,top:0,width:100,height:100};
|
|
12
|
+
const attrs=new Map(),styles=new Map();
|
|
13
|
+
const layer={hidden:true,inert:false,style:{getPropertyValue:k=>styles.get(k)?.[0]||'',getPropertyPriority:k=>styles.get(k)?.[1]||'',setProperty:(k,v,p='')=>styles.set(k,[v,p]),removeProperty:k=>styles.delete(k),get pointerEvents(){return styles.get('pointer-events')?.[0]||'';},set pointerEvents(v){styles.set('pointer-events',[v,'']);}},getAttribute:k=>attrs.get(k)??null,setAttribute:(k,v)=>attrs.set(k,v),removeAttribute:k=>attrs.delete(k),closest:()=>inner};
|
|
14
|
+
const root=()=>{const a=new Map();return {getBoundingClientRect:()=>rect,querySelectorAll:s=>s==='.blu-overlay,.blu-tour'?[layer]:[],addEventListener(){},removeEventListener(){},getAttribute:k=>a.get(k)??null,setAttribute:(k,v)=>a.set(k,v),removeAttribute:k=>a.delete(k),contains:()=>true};};
|
|
15
|
+
const outer=root(),inner=root();
|
|
16
|
+
const motion={mode,cancel(){},play(){return Promise.resolve('finished');},resolveOptions:()=>({duration:750}),animate(){let done;const p=new Promise(r=>done=r);jobs.push(done);return p;}};
|
|
17
|
+
const collection=enhanceContentMotion(outer,motion,'collection-layout');
|
|
18
|
+
const profile=enhanceContentMotion(inner,motion,'profile-card');
|
|
19
|
+
const change=hidden=>{layer.hidden=hidden;for(const w of watchers)if(!w.off&&w.options.attributeFilter?.length===1)w.fn();};
|
|
20
|
+
const finish=async()=>{for(const done of jobs.splice(0))done('finished');await Promise.resolve();await Promise.resolve();};
|
|
21
|
+
return {layer,styles,attrs,jobs,change,finish,destroy(){profile.destroy();collection.destroy();}};
|
|
22
|
+
}
|
|
23
|
+
test('a nested profile has one exit owner and remains visually hidden after close',async()=>{
|
|
24
|
+
const f=fixture();f.change(false);f.change(true);
|
|
25
|
+
assert.equal(f.jobs.length,1,'the collection must not animate its child dialog');
|
|
26
|
+
assert.equal(f.layer.inert,true);assert.equal(f.attrs.get('aria-hidden'),'true');
|
|
27
|
+
await f.finish();assert.equal(f.layer.hidden,true);assert.equal(f.styles.has('display'),false);assert.equal(f.layer.inert,false);assert.equal(f.attrs.has('aria-hidden'),false);f.destroy();
|
|
28
|
+
});
|
|
29
|
+
test('quick reopen and destroy restore exit styles without hiding the reopened dialog',async()=>{
|
|
30
|
+
const f=fixture();f.change(false);f.change(true);f.change(false);await f.finish();
|
|
31
|
+
assert.equal(f.layer.hidden,false);assert.equal(f.styles.has('display'),false);
|
|
32
|
+
f.change(true);f.destroy();await f.finish();assert.equal(f.styles.has('display'),false);assert.equal(f.layer.inert,false);
|
|
33
|
+
});
|
|
34
|
+
test('motion off closes immediately without temporary visibility styles',()=>{
|
|
35
|
+
const f=fixture('off');f.change(false);f.change(true);assert.equal(f.jobs.length,0);assert.equal(f.styles.has('display'),false);f.destroy();
|
|
36
|
+
});
|
package/test/isolation.test.mjs
CHANGED
|
@@ -5,7 +5,7 @@ import vm from 'node:vm';
|
|
|
5
5
|
const edition=JSON.parse(await readFile('edition.json','utf8'));
|
|
6
6
|
const sizes=JSON.parse(await readFile('dist/sizes.json','utf8'));
|
|
7
7
|
test('all seven groups expose the declared release component set without duplicates',()=>{
|
|
8
|
-
const names=Object.values(edition.nativeModules).flat();const expected=
|
|
8
|
+
const names=Object.values(edition.nativeModules).flat();const expected=67;assert.equal(names.length,expected);assert.equal(new Set(names).size,expected);assert.deepEqual([...names].sort(),[...edition.components].sort());assert.equal(Object.keys(edition.nativeModules).length,7);
|
|
9
9
|
});
|
|
10
10
|
test('non-grid entrypoints do not pull the AG Grid engine or unrelated component factories',async()=>{
|
|
11
11
|
for(const name of ['chat','chat-widget','float-button','date-picker','time-picker','date-time-picker','date-range-picker','font-provider','shape-mask','tree-select','transfer-list','core','layout','inputs','feedback','data']){
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { build } from 'esbuild';
|
|
4
|
+
const built = await build({ entryPoints: ['src/motion.ts'], bundle: true, format: 'esm', platform: 'node', write: false });
|
|
5
|
+
// Import must be safe without browser globals (server rendering/tooling).
|
|
6
|
+
const { createMotionController, resolveMotionMode, boundedTime, normalizeProgress, motionEffects, motionEasings, motionFrames, playStagger, staggerRanks, createScrollReveal, easeProgress } = await import('data:text/javascript;base64,' + Buffer.from(built.outputFiles[0].text).toString('base64'));
|
|
7
|
+
|
|
8
|
+
function browserFixture(reduced = false) {
|
|
9
|
+
const listeners = new Set(), attrs = new Map(), animations = [];
|
|
10
|
+
const media = { matches: reduced, addEventListener(_event, fn) { listeners.add(fn); }, removeEventListener(_event, fn) { listeners.delete(fn); } };
|
|
11
|
+
const scope = {
|
|
12
|
+
ownerDocument: { defaultView: { matchMedia: () => media, getComputedStyle: () => ({ getPropertyValue: key => ({ '--blu-motion-duration': '.28s', '--blu-motion-distance': '8px' }[key] || '') }) } },
|
|
13
|
+
contains: () => true, getAttribute: name => attrs.get(name) ?? null,
|
|
14
|
+
setAttribute: (name, value) => attrs.set(name, value), removeAttribute: name => attrs.delete(name),
|
|
15
|
+
};
|
|
16
|
+
const element = { animate(frames, options) {
|
|
17
|
+
let resolve, reject;
|
|
18
|
+
const finished = new Promise((yes, no) => { resolve = yes; reject = no; });
|
|
19
|
+
const animation = { frames, options, finished, cancel() { this.cancelled = true; reject(Error('Cancelled')); }, complete() { resolve(); } };
|
|
20
|
+
animations.push(animation); return animation;
|
|
21
|
+
} };
|
|
22
|
+
return { scope, element, animations, media, listeners, attrs, change(reduced) { media.matches = reduced; for (const fn of listeners) fn(); } };
|
|
23
|
+
}
|
|
24
|
+
test('preferences never override the device reduction and explicit Off always wins', () => {
|
|
25
|
+
for (const system of [true, false]) {
|
|
26
|
+
assert.equal(resolveMotionMode('off', system), 'off');
|
|
27
|
+
assert.equal(resolveMotionMode('reduced', system), 'reduced');
|
|
28
|
+
assert.equal(resolveMotionMode('system', system), system ? 'reduced' : 'full');
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
test('timing and progress reject invalid values rather than producing endless/false states', () => {
|
|
32
|
+
assert.equal(boundedTime(Infinity, 180), 180); assert.equal(boundedTime(-2, 180), 0); assert.equal(boundedTime(90000, 180), 2000);
|
|
33
|
+
assert.equal(normalizeProgress(NaN), undefined); assert.equal(normalizeProgress(Infinity), undefined); assert.equal(normalizeProgress(-10), 0); assert.equal(normalizeProgress(130), 100); assert.equal(normalizeProgress(42.5), 42.5);
|
|
34
|
+
});
|
|
35
|
+
test('starting another effect on the same element cancels and settles the first', async () => {
|
|
36
|
+
const f = browserFixture(), motion = createMotionController(f.scope);
|
|
37
|
+
const first = motion.play(f.element, 'slide-left'), last = motion.play(f.element, 'fade');
|
|
38
|
+
assert.equal(await first, 'cancelled'); assert.equal(f.animations[0].cancelled, true);
|
|
39
|
+
assert.equal(f.animations[1].options.duration, 280);
|
|
40
|
+
f.animations[1].complete(); assert.equal(await last, 'finished');
|
|
41
|
+
assert.equal(f.animations[1].cancelled, true, 'Finished effects must release fill state'); motion.destroy();
|
|
42
|
+
});
|
|
43
|
+
test('device preference changes cancel in-flight motion; reduced effects never translate', async () => {
|
|
44
|
+
const f = browserFixture(), motion = createMotionController(f.scope);
|
|
45
|
+
const first = motion.play(f.element, 'slide-up'); f.change(true);
|
|
46
|
+
assert.equal(await first, 'cancelled'); assert.equal(f.attrs.get('data-blu-motion'), 'reduced');
|
|
47
|
+
const reduced = motion.play(f.element, 'scale', { duration: 800, delay: 300 });
|
|
48
|
+
assert.equal(f.animations[1].options.duration, 200); assert.equal(f.animations[1].options.delay, 0);
|
|
49
|
+
assert.deepEqual(f.animations[1].frames, [{ opacity: 0 }, { opacity: 1 }]);
|
|
50
|
+
f.animations[1].complete(); assert.equal(await reduced, 'finished'); motion.destroy();
|
|
51
|
+
});
|
|
52
|
+
test('Off and unsupported animation APIs commit instantly without creating animation tasks', async () => {
|
|
53
|
+
const f = browserFixture(), motion = createMotionController(f.scope, 'off');
|
|
54
|
+
assert.equal(await motion.play(f.element), 'finished'); assert.equal(f.animations.length, 0);
|
|
55
|
+
motion.setPreference('system'); assert.equal(await motion.play({}), 'finished');
|
|
56
|
+
f.element.closest = () => ({});
|
|
57
|
+
assert.equal(await motion.play(f.element, 'slide-up', { duration: 800 }), 'finished');
|
|
58
|
+
assert.equal(f.animations.length, 0); motion.destroy();
|
|
59
|
+
});
|
|
60
|
+
test('destroy cancels pending effects, restores scope attributes, detaches listeners and allows remount', async () => {
|
|
61
|
+
const f = browserFixture(); f.attrs.set('data-blu-motion', 'previous');
|
|
62
|
+
const motion = createMotionController(f.scope), task = motion.play(f.element);
|
|
63
|
+
assert.throws(() => createMotionController(f.scope), /already/);
|
|
64
|
+
motion.destroy(); motion.destroy();
|
|
65
|
+
assert.equal(await task, 'cancelled'); assert.equal(f.listeners.size, 0); assert.equal(f.attrs.get('data-blu-motion'), 'previous');
|
|
66
|
+
assert.equal(await motion.play(f.element), 'cancelled'); assert.throws(() => motion.setPreference('off'), /destroyed/);
|
|
67
|
+
createMotionController(f.scope).destroy();
|
|
68
|
+
});
|
|
69
|
+
test('all base effects support both phases with opposite opacity endpoints and named easing', async () => {
|
|
70
|
+
for (const effect of motionEffects) for (const phase of ['enter', 'exit']) for (const easing of Object.keys(motionEasings)) {
|
|
71
|
+
const f = browserFixture(), motion = createMotionController(f.scope);
|
|
72
|
+
const task = motion.play(f.element, effect, { phase, easing, duration: 450 });
|
|
73
|
+
const animation = f.animations[0];
|
|
74
|
+
assert.equal(animation.frames[0].opacity, phase === 'enter' ? 0 : 1, effect);
|
|
75
|
+
assert.equal(animation.frames.at(-1).opacity, phase === 'enter' ? 1 : 0, effect);
|
|
76
|
+
assert.equal(animation.options.easing, motionEasings[easing]);
|
|
77
|
+
assert.equal(animation.options.duration, 450);
|
|
78
|
+
animation.complete(); assert.equal(await task, 'finished'); motion.destroy();
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
test('direction names describe movement for both entrance and exit', () => {
|
|
82
|
+
const expected = {
|
|
83
|
+
'slide-up': ['translateY(20px)', 'translateY(calc(-1 * 20px))'],
|
|
84
|
+
'slide-down': ['translateY(calc(-1 * 20px))', 'translateY(20px)'],
|
|
85
|
+
'slide-left': ['translateX(20px)', 'translateX(calc(-1 * 20px))'],
|
|
86
|
+
'slide-right': ['translateX(calc(-1 * 20px))', 'translateX(20px)'],
|
|
87
|
+
'zoom-in': ['scale(.92)', 'scale(1.08)'],
|
|
88
|
+
'zoom-out': ['scale(1.08)', 'scale(.92)'],
|
|
89
|
+
};
|
|
90
|
+
for (const [name, [enter, exit]] of Object.entries(expected)) {
|
|
91
|
+
assert.equal(motionFrames(name, 'enter', '20px')[0].transform, enter);
|
|
92
|
+
assert.equal(motionFrames(name, 'exit', '20px').at(-1).transform, exit);
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
test('every effect reduces to a short fade; zero duration remains instant', async () => {
|
|
96
|
+
for (const effect of motionEffects) for (const phase of ['enter', 'exit']) {
|
|
97
|
+
const f = browserFixture(true), motion = createMotionController(f.scope);
|
|
98
|
+
const task = motion.play(f.element, effect, { phase, easing: 'spring', delay: 500 });
|
|
99
|
+
assert.equal(f.animations[0].options.duration, 200);
|
|
100
|
+
assert.equal(f.animations[0].options.delay, 0);
|
|
101
|
+
assert.ok(f.animations[0].frames.every(frame => !('transform' in frame)));
|
|
102
|
+
f.animations[0].complete(); await task;
|
|
103
|
+
await motion.play(f.element, effect, { duration: 0 }); assert.equal(f.animations.length, 1);
|
|
104
|
+
motion.destroy();
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
test('stagger bounds total delay and removes all delays in reduced mode', async () => {
|
|
108
|
+
for (const reduced of [false, true]) {
|
|
109
|
+
const f = browserFixture(reduced), motion = createMotionController(f.scope);
|
|
110
|
+
const elements = Array.from({ length: 30 }, () => ({ animate: f.element.animate }));
|
|
111
|
+
const task = playStagger(motion, elements, 'slide-up', { gap: 120, delay: 40 });
|
|
112
|
+
const delays = f.animations.map(animation => animation.options.delay);
|
|
113
|
+
assert.equal(delays[0], reduced ? 0 : 40); assert.equal(delays.at(-1), reduced ? 0 : 500);
|
|
114
|
+
assert.ok(delays.every(delay => delay <= 500));
|
|
115
|
+
for (const animation of f.animations) animation.complete();
|
|
116
|
+
assert.ok((await task).every(result => result === 'finished')); motion.destroy();
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
test('scroll reveals only intersecting targets, defaults to once, and disposes pending motion', async () => {
|
|
120
|
+
const f = browserFixture(); let callback, disconnected = false, unobserved = [], count = 0;
|
|
121
|
+
f.scope.ownerDocument.defaultView.IntersectionObserver = class {
|
|
122
|
+
constructor(fn) { callback = fn; }
|
|
123
|
+
observe() {} unobserve(element) { unobserved.push(element); } disconnect() { disconnected = true; }
|
|
124
|
+
};
|
|
125
|
+
Object.assign(f.element, { ownerDocument: f.scope.ownerDocument, isConnected: true });
|
|
126
|
+
const motion = createMotionController(f.scope);
|
|
127
|
+
const reveal = createScrollReveal([f.element], motion, { onReveal() { count++; } });
|
|
128
|
+
callback([{ target: f.element, isIntersecting: false, intersectionRatio: 0 }]); assert.equal(count, 0);
|
|
129
|
+
callback([{ target: f.element, isIntersecting: true, intersectionRatio: .1 }]); assert.equal(count, 0);
|
|
130
|
+
callback([{ target: f.element, isIntersecting: true, intersectionRatio: .4 }]); assert.equal(count, 1); assert.deepEqual(unobserved, [f.element]);
|
|
131
|
+
callback([{ target: f.element, isIntersecting: false, intersectionRatio: 0 }]);
|
|
132
|
+
callback([{ target: f.element, isIntersecting: true, intersectionRatio: 1 }]); assert.equal(count, 1);
|
|
133
|
+
reveal.destroy(); assert.equal(disconnected, true); assert.equal(f.animations[0].cancelled, true);
|
|
134
|
+
callback([{ target: f.element, isIntersecting: true, intersectionRatio: 1 }]); assert.equal(count, 1); motion.destroy();
|
|
135
|
+
});
|
|
136
|
+
test('repeat scroll reveal waits for genuine re-entry; unsupported observer leaves content untouched', () => {
|
|
137
|
+
const f = browserFixture(); let callback, count = 0;
|
|
138
|
+
f.scope.ownerDocument.defaultView.IntersectionObserver = class { constructor(fn) { callback = fn; } observe() {} unobserve() {} disconnect() {} };
|
|
139
|
+
Object.assign(f.element, { ownerDocument: f.scope.ownerDocument, isConnected: true, hidden: false });
|
|
140
|
+
const motion = createMotionController(f.scope, 'off'), reveal = createScrollReveal([f.element], motion, { once: false, onReveal() { count++; } });
|
|
141
|
+
const entry = visible => [{ target: f.element, isIntersecting: visible, intersectionRatio: visible ? 1 : 0 }];
|
|
142
|
+
callback(entry(true)); callback(entry(true)); assert.equal(count, 1);
|
|
143
|
+
callback(entry(false)); callback(entry(true)); assert.equal(count, 2);
|
|
144
|
+
assert.equal(f.animations.length, 0); assert.equal(f.element.hidden, false);
|
|
145
|
+
reveal.destroy(); delete f.scope.ownerDocument.defaultView.IntersectionObserver;
|
|
146
|
+
const unsupported = createScrollReveal([f.element], motion); assert.equal(f.element.hidden, false);
|
|
147
|
+
unsupported.destroy(); createScrollReveal([], motion).destroy(); motion.destroy();
|
|
148
|
+
});
|
|
149
|
+
test('custom frames share cancellation; reduced and Off commit without spatial animation', async () => {
|
|
150
|
+
const f = browserFixture(), motion = createMotionController(f.scope);
|
|
151
|
+
const first = motion.animate(f.element, [{ height: '0px' }, { height: '80px' }]);
|
|
152
|
+
const second = motion.animate(f.element, [{ height: '30px' }, { height: '0px' }]);
|
|
153
|
+
assert.equal(await first, 'cancelled'); f.animations[1].complete(); assert.equal(await second, 'finished');
|
|
154
|
+
motion.setPreference('reduced'); await motion.animate(f.element, [{ transform: 'scale(.5)' }, { transform: 'none' }]); assert.equal(f.animations.length, 2);
|
|
155
|
+
motion.setPreference('off'); await motion.animate(f.element, [{ opacity: 0 }, { opacity: 1 }]); assert.equal(f.animations.length, 2); motion.destroy();
|
|
156
|
+
});
|
|
157
|
+
test('numeric interpolation respects actual time, easing, retargeting and disposal', async () => {
|
|
158
|
+
const f = browserFixture(), callbacks = new Map(); let id = 0, value = 0;
|
|
159
|
+
Object.assign(f.scope.ownerDocument.defaultView, { requestAnimationFrame(fn) { callbacks.set(++id, fn); return id; }, cancelAnimationFrame(key) { callbacks.delete(key); } });
|
|
160
|
+
function tick(time) { const batch = Array.from(callbacks.values()); callbacks.clear(); batch.forEach(fn => fn(time)); }
|
|
161
|
+
const motion = createMotionController(f.scope);
|
|
162
|
+
const first = motion.tween(f.element, 0, 100, v => value = v, { duration: 200, easing: 'linear' });
|
|
163
|
+
tick(0); tick(100); assert.equal(value, 50);
|
|
164
|
+
const last = motion.tween(f.element, value, 20, v => value = v, { duration: 200, easing: 'linear' });
|
|
165
|
+
assert.equal(await first, 'cancelled'); tick(101); tick(301); assert.equal(value, 20); assert.equal(await last, 'finished'); assert.equal(callbacks.size, 0);
|
|
166
|
+
const pending = motion.tween(f.element, 20, 200, v => value = v); motion.destroy(); assert.equal(await pending, 'cancelled'); assert.equal(callbacks.size, 0);
|
|
167
|
+
});
|
|
168
|
+
test('reduced numeric interpolation finishes instantly; invalid data cannot cancel valid work', async () => {
|
|
169
|
+
const f = browserFixture(true), motion = createMotionController(f.scope); let value;
|
|
170
|
+
assert.equal(await motion.tween(f.element, 0, 42, v => value = v), 'finished'); assert.equal(value, 42);
|
|
171
|
+
assert.throws(() => motion.tween(f.element, 0, NaN, () => {}), /finite/); motion.destroy();
|
|
172
|
+
});
|
|
173
|
+
test('numeric easing matches directional CSS behavior and exact endpoints', () => {
|
|
174
|
+
for (const easing of Object.keys(motionEasings)) { assert.equal(easeProgress(0, easing), 0); assert.equal(easeProgress(1, easing), 1); }
|
|
175
|
+
assert.equal(easeProgress(.5, 'linear'), .5);
|
|
176
|
+
assert.ok(easeProgress(.5, 'ease-in') < .5); assert.ok(easeProgress(.5, 'ease-out') > .5);
|
|
177
|
+
assert.ok(Math.abs(easeProgress(.5, 'ease-in-out') - .5) < .00001);
|
|
178
|
+
});
|
|
179
|
+
test('wipe directions change opposite clip edges on entry and exit', () => {
|
|
180
|
+
for (const effect of ['wipe-up', 'wipe-down', 'wipe-left', 'wipe-right']) {
|
|
181
|
+
const enter = motionFrames(effect), exit = motionFrames(effect, 'exit');
|
|
182
|
+
assert.notEqual(enter[0].clipPath, exit[1].clipPath); assert.equal(enter[1].clipPath, 'inset(0 0 0 0)'); assert.equal(exit[0].clipPath, enter[1].clipPath);
|
|
183
|
+
}
|
|
184
|
+
});
|
|
185
|
+
test('review durations up to two seconds remain intact; component default remains short', async () => {
|
|
186
|
+
const f = browserFixture(), motion = createMotionController(f.scope);
|
|
187
|
+
for (const duration of [750, 1000, 1500, 2000]) { const pending = motion.play(f.element, 'wipe-left', { duration }); assert.equal(f.animations.at(-1).options.duration, duration); f.animations.at(-1).complete(); await pending; }
|
|
188
|
+
const pending = motion.play(f.element); assert.equal(f.animations.at(-1).options.duration, 280); f.animations.at(-1).complete(); await pending; motion.destroy();
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
test('shared default is 450ms without styles; caller zero still bypasses animation', async () => {
|
|
192
|
+
const f=browserFixture();f.scope.ownerDocument.defaultView.getComputedStyle=()=>({getPropertyValue:()=>''});
|
|
193
|
+
const motion=createMotionController(f.scope);const task=motion.play(f.element);
|
|
194
|
+
assert.equal(f.animations[0].options.duration,450);f.animations[0].complete();await task;
|
|
195
|
+
await motion.play(f.element,'fade',{duration:0});assert.equal(f.animations.length,1);motion.destroy();
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
test('directional stagger ranks preserve stable order with even centers and incomplete grid rows', () => {
|
|
199
|
+
assert.deepEqual(staggerRanks(5, 'center'), [2,1,0,1,2]);
|
|
200
|
+
assert.deepEqual(staggerRanks(6, 'center'), [2,1,0,0,1,2]);
|
|
201
|
+
assert.deepEqual(staggerRanks(5, 'rows', 3), [0,0,0,1,1]);
|
|
202
|
+
assert.deepEqual(staggerRanks(5, 'columns', 3), [0,1,2,0,1]);
|
|
203
|
+
assert.deepEqual(staggerRanks(3, 'reverse'), [2,1,0]);
|
|
204
|
+
assert.deepEqual(staggerRanks(0, 'center'), []);
|
|
205
|
+
assert.throws(() => staggerRanks(3, 'rows', 0), /positive/);
|
|
206
|
+
assert.throws(() => staggerRanks(3, 'random'), /Unknown/);
|
|
207
|
+
});
|
|
208
|
+
test('anchored zoom applies only animation frames and disappears in reduced motion', async () => {
|
|
209
|
+
for (const reduced of [false, true]) {
|
|
210
|
+
const f = browserFixture(reduced), motion = createMotionController(f.scope);
|
|
211
|
+
const task = motion.play(f.element, 'zoom-in', { origin: 'top-right' });
|
|
212
|
+
assert.ok(f.animations[0].frames.every(frame => reduced ? !frame.transformOrigin : frame.transformOrigin === '100% 0%'));
|
|
213
|
+
f.animations[0].complete(); await task; motion.destroy();
|
|
214
|
+
}
|
|
215
|
+
});
|
package/dist/chunk-GXHANADL.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{a as l,b as R,c as y,d as w,e as v,f as x}from"./chunk-EOKNLCNS.js";function C(s,r,d,i){let e={...r},c=!1,u=w(s,e),h=l("label","blu-choice"),t=l("input","blu-choice__input"),m=l("span","blu-choice__mark"),a=l("span"),f=l("span","blu-choice__state");t.type=i==="radio"?"radio":"checkbox",i==="switch"&&t.setAttribute("role","switch"),h.classList.add("blu-choice--"+i),m.setAttribute("aria-hidden","true"),f.setAttribute("aria-hidden","true"),h.append(t,m,a,f),u.body.append(h);let n=v(u.root,k);function p(o){n.check(),c&&i==="radio"&&!("Checked"in o)&&(e.Checked=t.checked),e={...e,...o},u.paint(e,[t]),u.label.hidden=!0,u.label.removeAttribute("for"),x(t,e),t.checked=!!e.Checked,t.indeterminate=i==="checkbox"&&!!e.Indeterminate,t.value=e.Value||"on",y(t,"aria-readonly",e.ReadOnly?"true":void 0),a.textContent=(e.Label||"")+(e.Required?" *":""),f.textContent=t.checked?e.OnText||"":e.OffText||"",c=!0}function k(){p({Checked:!!r.Checked,Indeterminate:!!r.Indeterminate})}return t.addEventListener("click",o=>{e.ReadOnly&&o.preventDefault()},{signal:n.signal}),t.addEventListener("keydown",o=>{e.ReadOnly&&["ArrowLeft","ArrowRight","ArrowUp","ArrowDown"," "].includes(o.key)&&o.preventDefault()},{signal:n.signal}),t.addEventListener("change",()=>{e.Checked=t.checked,e.Indeterminate=!1,f.textContent=t.checked?e.OnText||"":e.OffText||"",d.Change?.(t.checked)},{signal:n.signal}),t.addEventListener("blur",()=>d.Blur?.(),{signal:n.signal}),p({}),{element:u.root,control:t,update:p,reset:k,getValue:()=>t.checked,focus:()=>t.focus(),validate:()=>t.reportValidity(),destroy:n.destroy}}function A(s,r={},d={}){return C(s,r,d,"checkbox")}function T(s,r={},d={}){return C(s,r,d,"switch")}function D(s,r,d={}){return C(s,r,d,"radio")}function V(s,r,d={}){let i={...r},e=[],c=w(s,i,!0),u=i.Name||R(),h=v(c.root,m);function t(a){h.check();let f=!e.length||"Options"in a;if(i={...i,...a},new Set(i.Options.map(n=>n.Value)).size!==i.Options.length)throw Error("Radio option values must be unique.");c.body.className="blu-radio-group blu-radio-group--"+(i.Direction||"Vertical").toLowerCase(),f&&(c.body.replaceChildren(),e=i.Options.map((n,p)=>{let k=l("label","blu-choice blu-choice--radio"),o=l("input","blu-choice__input"),E=l("span","blu-choice__mark"),g=l("span");o.type="radio",o.id=c.id+"-"+p,o.value=n.Value,E.setAttribute("aria-hidden","true");let O=l("span","",n.Label);if(O.id=o.id+"-label",g.append(O),o.setAttribute("aria-labelledby",O.id),n.Description){let b=l("small","blu-choice__description",n.Description);b.id=o.id+"-description",g.append(b)}return k.append(o,E,g),c.body.append(k),o.onchange=()=>{o.checked&&(i.Value=o.value,d.Change?.(o.value))},o.onclick=b=>{i.ReadOnly&&b.preventDefault()},o.onkeydown=b=>{i.ReadOnly&&["ArrowLeft","ArrowRight","ArrowUp","ArrowDown"," "].includes(b.key)&&b.preventDefault()},o.onblur=()=>d.Blur?.(),o})),c.paint(i,e),e.forEach((n,p)=>{x(n,i),n.name=i.Name||u,n.disabled=!!i.Disabled||!!i.Options[p].Disabled,n.checked=n.value===i.Value,y(n,"aria-readonly",i.ReadOnly?"true":void 0),i.Options[p].Description&&y(n,"aria-describedby",[n.getAttribute("aria-describedby"),n.id+"-description"].filter(Boolean).join(" "))})}function m(){t({Value:r.Value||""})}return t({}),{element:c.root,update:t,reset:m,getValue:()=>e.find(a=>a.checked)?.value||"",focus:()=>(e.find(a=>a.checked&&!a.disabled)||e.find(a=>!a.disabled))?.focus(),validate:()=>e.filter(a=>!a.disabled).every(a=>a.reportValidity()),destroy:h.destroy}}export{A as a,T as b,D as c,V as d};
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|