@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.
Files changed (179) hide show
  1. package/dist/build-audit.json +127 -65
  2. package/dist/checkbox.js +1 -1
  3. package/dist/chunk-4LHIQROI.js +0 -0
  4. package/dist/chunk-6T677NSC.js +1 -0
  5. package/dist/chunk-7CEOCZ5L.js +0 -0
  6. package/dist/chunk-7DXFNK7C.js +1 -0
  7. package/dist/chunk-CNIIMGT3.js +0 -0
  8. package/dist/chunk-CSTCEBEK.js +1 -0
  9. package/dist/chunk-DRZM2AL3.js +0 -0
  10. package/dist/{chunk-JVE6CXCJ.js → chunk-EPPETNK6.js} +1 -1
  11. package/dist/chunk-EPPSBHZR.js +1 -0
  12. package/dist/{chunk-NTFHNL7T.js → chunk-G6OW4OZM.js} +1 -1
  13. package/dist/chunk-GS67ZBLI.js +1 -0
  14. package/dist/{chunk-6EHKOVZK.js → chunk-HVR2UDFX.js} +1 -1
  15. package/dist/chunk-IKYBFMHI.js +1 -0
  16. package/dist/chunk-KVOA55NE.js +0 -0
  17. package/dist/{chunk-MZBB5ROJ.js → chunk-LF2HELEZ.js} +1 -1
  18. package/dist/{chunk-UEOCR2C3.js → chunk-OLDA73XG.js} +2 -2
  19. package/dist/{chunk-IZJN3EF7.js → chunk-T45FJNEQ.js} +1 -1
  20. package/dist/chunk-TZSX4OTV.js +1 -0
  21. package/dist/{chunk-GK3INPAZ.js → chunk-V2YPIC6Z.js} +1 -1
  22. package/dist/chunk-V4ZQLQ7L.js +0 -0
  23. package/dist/chunk-VAM3FPCS.js +0 -0
  24. package/dist/chunk-VSTKXZYY.js +0 -0
  25. package/dist/chunk-WG4US4JH.js +1 -0
  26. package/dist/chunk-WGSTNXAQ.js +0 -0
  27. package/dist/chunk-WJHCVECU.js +1 -0
  28. package/dist/chunk-Y4AXWTAD.js +0 -0
  29. package/dist/chunk-YLFXSHKZ.js +0 -0
  30. package/dist/chunk-ZDGZGAYG.js +0 -0
  31. package/dist/command-palette.js +1 -1
  32. package/dist/component-motion.css +39 -0
  33. package/dist/component-motion.d.ts +45 -0
  34. package/dist/component-motion.js +1 -0
  35. package/dist/content.js +1 -1
  36. package/dist/core.css +8 -0
  37. package/dist/core.js +1 -1
  38. package/dist/data-motion.css +4 -0
  39. package/dist/data-motion.d.ts +23 -0
  40. package/dist/data-motion.js +1 -0
  41. package/dist/data.js +1 -1
  42. package/dist/date-picker.js +1 -1
  43. package/dist/date-range-picker.js +1 -1
  44. package/dist/date-time-picker.js +1 -1
  45. package/dist/drawer.js +1 -1
  46. package/dist/essentials.css +8 -0
  47. package/dist/feedback.js +1 -1
  48. package/dist/form-layout.js +1 -1
  49. package/dist/index.js +1 -1
  50. package/dist/inputs.js +1 -1
  51. package/dist/layout.js +1 -1
  52. package/dist/modal.js +1 -1
  53. package/dist/motion/action-feedback.d.ts +14 -0
  54. package/dist/motion/border-glow.d.ts +12 -0
  55. package/dist/motion/button.d.ts +11 -0
  56. package/dist/motion/component-settings.d.ts +51 -0
  57. package/dist/motion/controller.d.ts +33 -0
  58. package/dist/motion/effects.d.ts +30 -0
  59. package/dist/motion/feedback.d.ts +6 -0
  60. package/dist/motion/loading.d.ts +9 -0
  61. package/dist/motion/optional.d.ts +35 -0
  62. package/dist/motion/photo.d.ts +7 -0
  63. package/dist/motion/presence.d.ts +12 -0
  64. package/dist/motion/scene.d.ts +10 -0
  65. package/dist/motion/screen.d.ts +6 -0
  66. package/dist/motion/scroll-reveal.d.ts +13 -0
  67. package/dist/motion/settings-group.d.ts +14 -0
  68. package/dist/motion/stagger.d.ts +12 -0
  69. package/dist/motion/structure.d.ts +18 -0
  70. package/dist/motion/tabs.d.ts +19 -0
  71. package/dist/motion/tokens.d.ts +11 -0
  72. package/dist/motion/value.d.ts +21 -0
  73. package/dist/motion/view-transition.d.ts +14 -0
  74. package/dist/motion-enhancers.d.ts +12 -0
  75. package/dist/motion-enhancers.js +1 -0
  76. package/dist/motion-settings.d.ts +3 -0
  77. package/dist/motion-settings.js +1 -0
  78. package/dist/motion.css +1076 -0
  79. package/dist/motion.d.ts +20 -0
  80. package/dist/motion.js +1 -0
  81. package/dist/native/form-layout.js +1 -1
  82. package/dist/native-motion-components.d.ts +3 -0
  83. package/dist/native-motion-content.d.ts +1 -0
  84. package/dist/native-motion-foundations.d.ts +3 -0
  85. package/dist/native-motion-layout.d.ts +3 -0
  86. package/dist/native-motion-pilot.d.ts +34 -0
  87. package/dist/native-motion.d.ts +71 -0
  88. package/dist/notification.js +1 -1
  89. package/dist/pagination.js +1 -1
  90. package/dist/radio-button.js +1 -1
  91. package/dist/radio-group.js +1 -1
  92. package/dist/sidebar.js +1 -1
  93. package/dist/sizes.json +273 -151
  94. package/dist/skeleton.css +8 -0
  95. package/dist/styles.css +1130 -0
  96. package/dist/switch.js +1 -1
  97. package/dist/table.js +1 -1
  98. package/dist/time-picker.js +1 -1
  99. package/dist/tree-view.js +1 -1
  100. package/dist/web-motion/chip-ripple.d.ts +6 -0
  101. package/dist/web-motion/choice-ripple.d.ts +6 -0
  102. package/dist/web-motion/content.d.ts +9 -0
  103. package/dist/web-motion/data-rows.d.ts +12 -0
  104. package/dist/web-motion/fields.d.ts +6 -0
  105. package/dist/web-motion/navigation.d.ts +6 -0
  106. package/dist/web-motion/notifications.d.ts +6 -0
  107. package/dist/web-motion/select-ripple.d.ts +6 -0
  108. package/dist/web-motion/statistic.d.ts +13 -0
  109. package/dist/web-motion/surfaces.d.ts +10 -0
  110. package/dist/web-motion/tree.d.ts +6 -0
  111. package/docs/FORM-LAYOUT-ESSENTIALS.md +4 -4
  112. package/edition.json +1 -1
  113. package/npm-shrinkwrap.json +2 -2
  114. package/package.json +29 -3
  115. package/scripts/build-motion-css.mjs +36 -0
  116. package/scripts/build-motion-styles.mjs +29 -0
  117. package/scripts/build.mjs +5 -1
  118. package/scripts/motion-entries.mjs +2 -0
  119. package/src/component-motion.ts +101 -0
  120. package/src/data-motion.ts +60 -0
  121. package/src/form-layout/choice.ts +17 -1
  122. package/src/motion/action-feedback.ts +38 -0
  123. package/src/motion/border-glow.ts +41 -0
  124. package/src/motion/button.ts +56 -0
  125. package/src/motion/component-settings.ts +147 -0
  126. package/src/motion/controller.ts +191 -0
  127. package/src/motion/effects.ts +75 -0
  128. package/src/motion/feedback.ts +41 -0
  129. package/src/motion/loading.ts +40 -0
  130. package/src/motion/optional.ts +123 -0
  131. package/src/motion/photo.ts +19 -0
  132. package/src/motion/presence.ts +44 -0
  133. package/src/motion/scene.ts +103 -0
  134. package/src/motion/screen.ts +15 -0
  135. package/src/motion/scroll-reveal.ts +49 -0
  136. package/src/motion/settings-group.ts +28 -0
  137. package/src/motion/stagger.ts +26 -0
  138. package/src/motion/structure.ts +75 -0
  139. package/src/motion/tabs.ts +105 -0
  140. package/src/motion/tokens.ts +2 -0
  141. package/src/motion/value.ts +61 -0
  142. package/src/motion/view-transition.ts +57 -0
  143. package/src/motion-enhancers.ts +12 -0
  144. package/src/motion-settings.ts +3 -0
  145. package/src/motion.ts +20 -0
  146. package/src/native-motion-components.ts +76 -0
  147. package/src/native-motion-content.ts +10 -0
  148. package/src/native-motion-foundations.ts +56 -0
  149. package/src/native-motion-layout.ts +35 -0
  150. package/src/native-motion-pilot.ts +151 -0
  151. package/src/native-motion.ts +188 -0
  152. package/src/web-motion/chip-ripple.ts +31 -0
  153. package/src/web-motion/choice-ripple.ts +30 -0
  154. package/src/web-motion/content.ts +74 -0
  155. package/src/web-motion/data-rows.ts +106 -0
  156. package/src/web-motion/fields.ts +52 -0
  157. package/src/web-motion/navigation.ts +62 -0
  158. package/src/web-motion/notifications.ts +67 -0
  159. package/src/web-motion/select-ripple.ts +23 -0
  160. package/src/web-motion/statistic.ts +23 -0
  161. package/src/web-motion/surfaces.ts +137 -0
  162. package/src/web-motion/tree.ts +44 -0
  163. package/styles/component-motion.css +39 -0
  164. package/styles/data-motion.css +4 -0
  165. package/styles/motion-optional.css +15 -0
  166. package/styles/motion-presets.css +92 -0
  167. package/styles/motion-tokens.css +6 -0
  168. package/styles/motion.css +125 -0
  169. package/styles/skeleton.css +8 -0
  170. package/test/component-motion-settings.test.mjs +35 -0
  171. package/test/content-overlay-motion.test.mjs +36 -0
  172. package/test/isolation.test.mjs +1 -1
  173. package/test/motion.test.mjs +215 -0
  174. package/dist/chunk-GXHANADL.js +0 -1
  175. /package/dist/{chunk-3PNBQVYS.js → chunk-2BSBXDOT.js} +0 -0
  176. /package/dist/{chunk-H7ETPJVW.js → chunk-3PODK22R.js} +0 -0
  177. /package/dist/{chunk-SIU7DSOA.js → chunk-KBWB4OIF.js} +0 -0
  178. /package/dist/{chunk-OPMD7QNV.js → chunk-W3H7XTA7.js} +0 -0
  179. /package/dist/{chunk-XWXEH3SW.js → chunk-XVMJG26X.js} +0 -0
package/dist/switch.js CHANGED
@@ -1 +1 @@
1
- import"./chunk-EPZGLHPD.js";import{b as a}from"./chunk-GXHANADL.js";import"./chunk-EOKNLCNS.js";export{a as createSwitch};
1
+ import"./chunk-EPZGLHPD.js";import{b as a}from"./chunk-TZSX4OTV.js";import"./chunk-EOKNLCNS.js";export{a as createSwitch};
package/dist/table.js CHANGED
@@ -1 +1 @@
1
- import{a}from"./chunk-IZJN3EF7.js";import"./chunk-EOKNLCNS.js";export{a as createTable};
1
+ import"./chunk-YLFXSHKZ.js";import{b as a}from"./chunk-T45FJNEQ.js";import"./chunk-EOKNLCNS.js";export{a as createTable};
@@ -1 +1 @@
1
- import{a}from"./chunk-JVE6CXCJ.js";import"./chunk-MEEQNPGM.js";import"./chunk-5TEIXA4R.js";import"./chunk-SDMH2EVD.js";export{a as createTimePicker};
1
+ import"./chunk-V4ZQLQ7L.js";import{a}from"./chunk-EPPETNK6.js";import"./chunk-MEEQNPGM.js";import"./chunk-5TEIXA4R.js";import"./chunk-SDMH2EVD.js";export{a as createTimePicker};
package/dist/tree-view.js CHANGED
@@ -1 +1 @@
1
- import{a}from"./chunk-H7ETPJVW.js";import"./chunk-5TEIXA4R.js";import"./chunk-SDMH2EVD.js";export{a as createTreeView};
1
+ import"./chunk-ZDGZGAYG.js";import{a}from"./chunk-3PODK22R.js";import"./chunk-5TEIXA4R.js";import"./chunk-SDMH2EVD.js";export{a as createTreeView};
@@ -0,0 +1,6 @@
1
+ import type { MotionController } from '../motion/controller.js';
2
+ /** Decorate the selected chip after its real click, even if that click replaces
3
+ * its button. No synthesized activation or application-state change. */
4
+ export declare function enhanceChipRipple(root: HTMLElement, motion: MotionController): {
5
+ destroy(): void;
6
+ };
@@ -0,0 +1,6 @@
1
+ import type { MotionController, MotionOptions } from '../motion/controller.js';
2
+ /** One decorative pulse per native activation. Label clicks, Space and radio
3
+ * arrow changes keep the original input, focus, value and change events. */
4
+ export declare function enhanceChoiceRipple(root: HTMLElement, motion: MotionController, timing?: MotionOptions): {
5
+ destroy(): void;
6
+ };
@@ -0,0 +1,9 @@
1
+ import type { MotionController } from '../motion/controller.js';
2
+ export type ContentMotionKind = 'profile-card' | 'video-lightbox' | 'video-player' | 'collection-layout' | 'cascader' | 'transfer' | 'chip' | 'tree-select' | 'timeline' | 'skeleton' | 'media-image' | 'tour' | 'notification-center';
3
+ /** DOM-only enhancement: the original component still owns data, focus and media playback. */
4
+ export declare function enhanceContentMotion(root: HTMLElement, motion: MotionController, kind: ContentMotionKind, options?: {
5
+ timelineKeys?: () => string[];
6
+ }): {
7
+ capture: () => void;
8
+ destroy(): void;
9
+ };
@@ -0,0 +1,12 @@
1
+ import type { MotionController } from '../motion/controller.js';
2
+ export interface MotionRow {
3
+ key: string;
4
+ element: HTMLElement;
5
+ cells: HTMLElement[];
6
+ cellKeys: string[];
7
+ }
8
+ /** Presentation only. Data, selection, sorting, paging and keyboard handling stay with the component. */
9
+ export declare function enhanceDataRows(root: HTMLElement, motion: MotionController, read: () => MotionRow[]): {
10
+ capture: () => void;
11
+ destroy(): void;
12
+ };
@@ -0,0 +1,6 @@
1
+ import type { MotionController } from '../motion/controller.js';
2
+ /** Adds feedback to existing controls without replacing inputs or intercepting native events. */
3
+ export declare function enhanceField(root: HTMLElement, motion: MotionController): {
4
+ sync: () => void;
5
+ destroy(): void;
6
+ };
@@ -0,0 +1,6 @@
1
+ import type { MotionController } from '../motion/controller.js';
2
+ /** The existing Sidebar/Pagination still owns navigation, focus and events.
3
+ * This marker only follows aria-current; it never intercepts links or history. */
4
+ export declare function enhanceNavigation(root: HTMLElement, motion: MotionController, kind: 'sidebar' | 'pagination' | 'tabs'): {
5
+ destroy(): void;
6
+ };
@@ -0,0 +1,6 @@
1
+ import type { MotionController } from '../motion/controller.js';
2
+ /** Observe the existing message stack. Its runtime still owns timers, pause on
3
+ * hover/focus, announcements and dismissal. Exits are decorative, inert copies. */
4
+ export declare function enhanceNotifications(root: HTMLElement, motion: MotionController): {
5
+ destroy(): void;
6
+ };
@@ -0,0 +1,6 @@
1
+ import type { MotionController, MotionOptions } from '../motion/controller.js';
2
+ /** Decorate an actual open/close or selection change, after Select has updated
3
+ * its label. The component continues to own focus, keyboard and value events. */
4
+ export declare function enhanceSelectRipple(root: HTMLElement, motion: MotionController, timing?: MotionOptions): {
5
+ destroy(): void;
6
+ };
@@ -0,0 +1,13 @@
1
+ import type { MotionController } from '../motion/controller.js';
2
+ type Values = {
3
+ Value?: number;
4
+ Precision?: number;
5
+ Locale?: string;
6
+ IsLoading?: boolean;
7
+ };
8
+ /** Exact value remains accessible throughout the decorative number transition. */
9
+ export declare function enhanceStatistic(root: HTMLElement, motion: MotionController, read: () => Values): {
10
+ capture(): void;
11
+ destroy(): void;
12
+ };
13
+ export {};
@@ -0,0 +1,10 @@
1
+ import type { MotionController } from '../motion/controller.js';
2
+ /** Existing components own focus trapping, dismissal and events; the first dialog
3
+ * presentation receives named context after paint. A closing surface is an inert,
4
+ * short-lived visual copy; its bounds are frozen so focus restoration cannot reflow it.
5
+ */
6
+ export declare function observeSurface(root: HTMLElement, motion: MotionController, kind: 'modal' | 'drawer' | 'palette' | 'select' | 'picker' | 'cascader' | 'tree-select'): {
7
+ sync: () => void;
8
+ capture: () => void;
9
+ destroy(): void;
10
+ };
@@ -0,0 +1,6 @@
1
+ import type { MotionController } from '../motion/controller.js';
2
+ /** The tree runtime owns hidden/expanded state and keyboard navigation. During a
3
+ * full-motion collapse only, keep the inert visual in layout until it reaches zero. */
4
+ export declare function enhanceTree(root: HTMLElement, motion: MotionController): {
5
+ destroy(): void;
6
+ };
@@ -1,6 +1,6 @@
1
- # Community Form & Layout Essentials — local draft
1
+ # Community Form & Layout Essentials
2
2
 
3
- This is unreleased development work. The npm and native 1.0.1 releases do not contain these additions. No version has been changed and no package, website, or native module has been published. `edition.json` continues to describe the existing release; `draft-components.json` explicitly tracks this batch. Native source preparation is not proof of Service Studio editor or runtime behavior.
3
+ These 12 controls and 18 native Blocks shipped in Community 1.1.0. The isolated 1.2.0 review candidate adds opt-in motion and retains their existing APIs. Public 1.2 publication is held. This is not a full-library accessibility certification.
4
4
 
5
5
  ## Local preview and build
6
6
 
@@ -86,7 +86,7 @@ Table columns accept `Key`, `Header`, `Sortable`, `Align`, and optional text-onl
86
86
 
87
87
  ## Native local implementation and verification boundary
88
88
 
89
- Eighteen public native blocks are implemented and merged into the local Service Studio editors for `BluKavantUi_Community_Inputs`, `BluKavantUi_Community_Layout`, and `BluKavantUi_Community_Data`, under `Essentials`. Each block has a custom icon. Local OML review copies, original backups, model-edit source, validation results, and dependency inventories are saved in `native-builds/FormLayoutEssentials20261022/`. These are unpublished development copies; the released OMLs and npm archives are unchanged.
89
+ Eighteen public native blocks are implemented in the Service Studio producers for `BluKavantUi_Community_Inputs`, `BluKavantUi_Community_Layout`, and `BluKavantUi_Community_Data`, under `Essentials`. Each block has a custom icon. Local OML review copies, original backups, model-edit source, validation results, and dependency inventories are saved in `native-builds/FormLayoutEssentials20261022/`. These are unpublished development copies; the released OMLs and npm archives are unchanged.
90
90
 
91
91
  All three producers pass Service Studio validation with zero errors and only their two pre-existing license/notice resource warnings. The actual **Remove Unused Dependencies** command was run on each producer; all reported no dependencies to remove. Remaining references are `(System)` and, for Inputs/Data, `BluKavantUi_Community_Core`. There is no OutSystems UI or Material UI dependency. Platform Container, Expression, Image, and Placeholder widgets support editor display; they are distinct from the OutSystems UI producer.
92
92
 
@@ -119,4 +119,4 @@ OnReady and OnParametersChanged synchronize each control; OnDestroy disposes it.
119
119
 
120
120
  Consumers own values and application validation: assign the returned OnChange value to the variable passed into the block, and pass application validation messages through `Error`. These custom HTML controls do **not** automatically register with OutSystems `Form.Valid`; the consumer must validate its model before saving. Browser constraint validation and the web `validate()` API remain available for ordinary HTML forms.
121
121
 
122
- No running OutSystems consumer was published or exercised. Runtime integration in a compiled OutSystems application remains a required pre-release check once publication is authorized. No dependency refresh or publication of the existing demo consumers was performed. FormBuilder/FormRenderer remain Pro.
122
+ Compiled development consumers have been exercised. See the 1.2 review evidence for exact keyboard, VoiceOver and motion coverage; this does not establish coverage of every consumer application. Public publication remains held. FormBuilder/FormRenderer remain Pro.
package/edition.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "edition": "Community",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
4
4
  "license": "BluKavantUI Community Free Use, Official Distribution Licence 1.2",
5
5
  "distribution": "Official @blukavantui/community npm package, official BluKavantUI Web downloads and official OutSystems Forge listing when published",
6
6
  "redistribution": "No republishing, mirroring or resale of the reusable components, including modified copies; see LICENSE.txt for permitted private work, application delivery and third-party rights.",
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@blukavantui/community",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@blukavantui/community",
9
- "version": "1.1.0",
9
+ "version": "1.2.0",
10
10
  "license": "SEE LICENSE IN LICENSE.txt",
11
11
  "dependencies": {
12
12
  "ag-grid-community": "36.2.0"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blukavantui/community",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
4
4
  "description": "BluKavantUI Community: 67 Web components. OutSystems integration available separately.",
5
5
  "homepage": "https://blukavantui.blutekmedia.com/community",
6
6
  "bugs": {
@@ -414,7 +414,30 @@
414
414
  "types": "./dist/form-layout.d.ts",
415
415
  "import": "./dist/form-layout.js"
416
416
  },
417
- "./form-layout.css": "./dist/form-layout.css"
417
+ "./form-layout.css": "./dist/form-layout.css",
418
+ "./motion": {
419
+ "types": "./dist/motion.d.ts",
420
+ "import": "./dist/motion.js"
421
+ },
422
+ "./motion.css": "./dist/motion.css",
423
+ "./component-motion": {
424
+ "types": "./dist/component-motion.d.ts",
425
+ "import": "./dist/component-motion.js"
426
+ },
427
+ "./data-motion": {
428
+ "types": "./dist/data-motion.d.ts",
429
+ "import": "./dist/data-motion.js"
430
+ },
431
+ "./motion/settings": {
432
+ "types": "./dist/motion-settings.d.ts",
433
+ "import": "./dist/motion-settings.js"
434
+ },
435
+ "./motion/enhancers": {
436
+ "types": "./dist/motion-enhancers.d.ts",
437
+ "import": "./dist/motion-enhancers.js"
438
+ },
439
+ "./component-motion.css": "./dist/component-motion.css",
440
+ "./data-motion.css": "./dist/data-motion.css"
418
441
  },
419
442
  "files": [
420
443
  "dist",
@@ -432,7 +455,10 @@
432
455
  "docs/PUBLISHING.md",
433
456
  "assets",
434
457
  "draft-components.json",
435
- "docs/FORM-LAYOUT-ESSENTIALS.md"
458
+ "docs/FORM-LAYOUT-ESSENTIALS.md",
459
+ "scripts/build-motion-css.mjs",
460
+ "scripts/build-motion-styles.mjs",
461
+ "scripts/motion-entries.mjs"
436
462
  ],
437
463
  "sideEffects": [
438
464
  "**/*.css",
@@ -0,0 +1,36 @@
1
+ /** Web-only utility generation from the same presets as WAAPI. Does not touch native/release artifacts. */
2
+ import { build } from 'esbuild';
3
+ import { readFile, writeFile } from 'node:fs/promises';
4
+ import { fileURLToPath } from 'node:url';
5
+ const root = fileURLToPath(new URL('../', import.meta.url));
6
+ const result = await build({ absWorkingDir: root, entryPoints: ['src/motion/effects.ts'], bundle: true, format: 'esm', platform: 'node', write: false });
7
+ const { motionEffects, motionEasings, motionFrames, motionClass, motionEasingClass, motionOrigins } = await import('data:text/javascript;base64,' + Buffer.from(result.outputFiles[0].text).toString('base64'));
8
+ const lines = ['/* Generated by scripts/build-motion-css.mjs. Edit src/motion/effects.ts, then regenerate. */'];
9
+ for (const effect of motionEffects) {
10
+ for (const phase of ['enter', 'exit']) {
11
+ const name = motionClass(effect, phase);
12
+ const aliases = effect === 'fade' ? [`.blu-motion-fade-${phase === 'enter' ? 'in' : 'out'}`]
13
+ : phase === 'enter' && effect === 'slide-up' ? ['.blu-motion-slide-up']
14
+ : phase === 'enter' && effect === 'zoom-in' ? ['.blu-motion-scale-in'] : [];
15
+ lines.push([`.${name}`, ...aliases].join(', ') + ` { animation: ${name} var(--blu-motion-duration) var(--blu-motion-ease) var(--blu-motion-delay) both; }`);
16
+ const frames = motionFrames(effect, phase, 'var(--blu-motion-distance)');
17
+ lines.push(`@keyframes ${name} { ${frames.map((frame, index) => {
18
+ const percent = (frame.offset ?? index / (frames.length - 1)) * 100;
19
+ return `${percent}% { ${Object.entries(frame).filter(([key]) => key !== 'offset').map(([key, value]) => `${key.replace(/[A-Z]/g, letter => "-" + letter.toLowerCase())}: ${value};`).join(' ')} }`;
20
+ }).join(' ')} }`);
21
+ }
22
+ }
23
+ for (const [name, curve] of Object.entries(motionEasings)) lines.push(`.${motionEasingClass(name)} { --blu-motion-ease: ${curve}; }`);
24
+ for (const [name, origin] of Object.entries(motionOrigins)) lines.push(`.blu-motion-origin-${name} { transform-origin: ${origin}; }`);
25
+ // Exit utilities do not manage hidden/inert. Owners must do that after animationend or use createMotionPresence.
26
+ const css = lines.join('\n') + '\n';
27
+ const target = new URL('../styles/motion-presets.css', import.meta.url);
28
+ if (process.argv.includes('--check')) {
29
+ if (await readFile(target, 'utf8') !== css) throw Error('Motion CSS is stale. Run node scripts/build-motion-css.mjs.');
30
+ } else await writeFile(target, css);
31
+
32
+ const tokenBuild = await build({absWorkingDir:root,entryPoints:['src/motion/tokens.ts'],bundle:true,format:'esm',platform:'node',write:false});
33
+ const {motionTiming} = await import('data:text/javascript;base64,'+Buffer.from(tokenBuild.outputFiles[0].text).toString('base64'));
34
+ const tokenCSS = '/* Generated from src/motion/tokens.ts. */\n:root {\n'+['fast','normal','slow'].map(key=>` --blu-motion-${key}: ${motionTiming[key]}ms;`).join('\n')+'\n}\n';
35
+ const tokenTarget = new URL('../styles/motion-tokens.css',import.meta.url);
36
+ if(process.argv.includes('--check')) {if(await readFile(tokenTarget,'utf8')!==tokenCSS)throw Error('Motion tokens CSS is stale.');} else await writeFile(tokenTarget,tokenCSS);
@@ -0,0 +1,29 @@
1
+ import { build } from 'esbuild';
2
+ import { mkdir, readFile, writeFile } from 'node:fs/promises';
3
+ import { resolve } from 'node:path';
4
+ import { fileURLToPath } from 'node:url';
5
+
6
+ /** The same stylesheet is used by the package build and local documentation. */
7
+ export async function buildMotionStyles(outdir) {
8
+ const root = fileURLToPath(new URL('../', import.meta.url));
9
+ const result = await build({ absWorkingDir: root, entryPoints: ['styles/motion.css'],
10
+ bundle: true, write: false, target: ['safari16.4', 'chrome110'], legalComments: 'inline' });
11
+ const css = result.outputFiles[0].text;
12
+ await mkdir(outdir, { recursive: true });
13
+ await writeFile(resolve(outdir, 'motion.css'), css);
14
+ return css;
15
+ }
16
+
17
+ /** Call after generating the base full stylesheet; used by the normal release build. */
18
+ export async function includeMotionStyles(outdir) {
19
+ const base = await readFile(resolve(outdir, 'styles.css'), 'utf8');
20
+ const motion = await buildMotionStyles(outdir);
21
+ let combined = motion;
22
+ for (const name of ['component-motion', 'data-motion']) {
23
+ const css = await readFile(new URL('../styles/' + name + '.css', import.meta.url), 'utf8');
24
+ await writeFile(resolve(outdir, name + '.css'), css);
25
+ combined += '\n' + css;
26
+ }
27
+ await writeFile(resolve(outdir, 'styles.css'), base + '\n' + combined);
28
+ return motion;
29
+ }
package/scripts/build.mjs CHANGED
@@ -1,3 +1,6 @@
1
+ import './build-motion-css.mjs';
2
+ import { motionEntries } from './motion-entries.mjs';
3
+ import { includeMotionStyles } from './build-motion-styles.mjs';
1
4
  import {build} from 'esbuild';
2
5
  import {readFile,writeFile,mkdir,rm,cp} from 'node:fs/promises';
3
6
  import {execFileSync} from 'node:child_process';
@@ -9,7 +12,7 @@ const essentials=['button','badge','chip','chip-group','card','skeleton','empty-
9
12
  const handwritten=new Set(draft.components.map(c=>c.slug));
10
13
  const additions=edition.components.map(slug).filter(n=>!essentials.includes(n)&&n!=='grid-basic'&&!handwritten.has(n));
11
14
  const groups=Object.fromEntries(Object.entries(edition.nativeModules).map(([k,v])=>[k.split('_').at(-1).toLowerCase(),v.map(slug)]));
12
- const entries=[...new Set(['index','grid-basic','essentials','theme','form-layout',...draft.components.map(c=>c.slug),...essentials,...additions,...Object.keys(groups)])];
15
+ const entries=[...new Set(['index','grid-basic','essentials','theme','form-layout',...motionEntries,...draft.components.map(c=>c.slug),...essentials,...additions,...Object.keys(groups)])];
13
16
  await rm('dist',{recursive:true,force:true});await mkdir('dist');
14
17
  const web=await build({entryPoints:entries.map(n=>'src/'+n+'.ts'),bundle:true,splitting:true,format:'esm',target:'es2020',minify:true,outdir:'dist',metafile:true,preserveSymlinks:true});
15
18
  const nativeEntries={'bluui-community':'src/native.ts','bluui-community-grid':'src/native-grid.ts','native/form-layout':'src/native-components/form-layout.ts'};
@@ -32,6 +35,7 @@ await cp('styles/form-layout.css','dist/form-layout.css');
32
35
  for(const c of draft.components)await cp(`styles/${c.slug}.css`,`dist/${c.slug}.css`);
33
36
  for(const group of new Set(draft.components.map(c=>c.group)))await writeFile('dist/'+group+'.css',await readFile('dist/'+group+'.css','utf8')+'\n'+draftStyles);
34
37
  await writeFile('dist/styles.css',await readFile('dist/styles.css','utf8')+'\n'+draftStyles);
38
+ await includeMotionStyles('dist');
35
39
  await cp('draft-components.json','dist/draft-components.json');
36
40
  const inputs=[...new Set([...Object.keys(web.metafile.inputs),...Object.keys(native.metafile.inputs)])];
37
41
  for(const file of inputs)if(!file.startsWith('src/')&&!file.startsWith('node_modules/ag-grid-community/')&&!file.startsWith('node_modules/ag-stack/'))throw Error('Unexpected source in Community bundle: '+file);
@@ -0,0 +1,2 @@
1
+ /** Shared by the normal build and isolated local package verification. */
2
+ export const motionEntries = ['motion', 'motion-settings', 'motion-enhancers', 'component-motion', 'data-motion'];
@@ -0,0 +1,101 @@
1
+ /** Local, opt-in web integration. Import after the component styles plus component-motion.css.
2
+ * Create the controller on a scope containing every component host (normally document.body).
3
+ * Dispose this collection before disposing its controller. No native entry points import this file.
4
+ */
5
+ import type { MotionController } from './motion/controller.js';
6
+ import { createComponentMotionScope, validateComponentMotion } from './motion/component-settings.js';
7
+ import type { ComponentMotionSettings, ComponentMotionHandle } from './motion/component-settings.js';
8
+ export type { ComponentMotionSettings, ComponentMotionHandle } from './motion/component-settings.js';
9
+ import { createMotionButton } from './motion/button.js';
10
+ import { createPressFeedback } from './motion/feedback.js';
11
+ import { createModal } from './component-factories/modal.js';
12
+ import { createDrawer } from './component-factories/drawer.js';
13
+ import { createCommandPalette } from './component-factories/command-palette.js';
14
+ import { createTextInput } from './text-input.js';
15
+ import { createTextArea } from './text-area.js';
16
+ import { createCheckbox } from './checkbox.js';
17
+ import { createSwitch } from './switch.js';
18
+ import { createRadioButton } from './radio-button.js';
19
+ import { createRadioGroup } from './radio-group.js';
20
+ import { createSelect } from './select.js';
21
+ import { createFormField } from './form-field.js';
22
+ import { observeSurface } from './web-motion/surfaces.js';
23
+ import { enhanceField } from './web-motion/fields.js';
24
+ import { createDatePicker } from './component-factories/date-picker.js';
25
+ import { createTimePicker } from './component-factories/time-picker.js';
26
+ import { createDateTimePicker } from './component-factories/date-time-picker.js';
27
+ import { createDateRangePicker } from './component-factories/date-range-picker.js';
28
+ import { createTreeView } from './component-factories/tree-view.js';
29
+ import { createNotification } from './component-factories/notification.js';
30
+ import { enhanceTree } from './web-motion/tree.js';
31
+ import { enhanceNotifications } from './web-motion/notifications.js';
32
+ import { createSidebar } from './component-factories/sidebar.js';
33
+ import { createPagination } from './component-factories/pagination.js';
34
+ import { enhanceNavigation } from './web-motion/navigation.js';
35
+
36
+ type Instance = { element: HTMLElement; destroy(): void };
37
+ type Enhancement = { destroy(): void };
38
+ export function createMotionComponents(motion: MotionController, initial: ComponentMotionSettings = {}) {
39
+ let defaults = validateComponentMotion(initial), disposed = false;
40
+ const instances = new Set<Instance>();
41
+ const configurations = new Map<Instance, () => void>();
42
+ function decorate<A extends unknown[], C extends Instance>(factory: (...args: A) => C, enhance: (component: C, local: MotionController) => Enhancement) {
43
+ return (...args: A): C & ComponentMotionHandle => {
44
+ if (disposed) throw Error('Motion components are destroyed.');
45
+ const component = factory(...args);
46
+ const scoped = createComponentMotionScope(component.element, motion, defaults);
47
+ let extra: Enhancement;
48
+ try { extra = enhance(component, scoped.motion); } catch (error) { scoped.destroy(); component.destroy(); throw error; }
49
+ const destroy = component.destroy.bind(component); let dead = false, overrides: ComponentMotionSettings = {};
50
+ const apply = () => scoped.update({ ...defaults, ...overrides });
51
+ const result = Object.assign(component, { setMotion(settings: ComponentMotionSettings = {}) {
52
+ if (dead) throw Error('Component is destroyed.');
53
+ overrides = validateComponentMotion(settings);
54
+ if (overrides.mode === 'inherit') delete overrides.mode;
55
+ apply();
56
+ } });
57
+ component.destroy = () => { if (dead) return; dead = true; extra.destroy(); scoped.destroy(); destroy(); instances.delete(component); configurations.delete(component); };
58
+ configurations.set(component, apply); instances.add(component); return result;
59
+ };
60
+ }
61
+ const field = (component: Instance, local: MotionController) => enhanceField(component.element, local);
62
+ const surface = (kind: 'modal' | 'drawer' | 'palette') => (component: Instance, local: MotionController) => observeSurface(component.element, local, kind);
63
+ const picker = (component: Instance, local: MotionController) => observeSurface(component.element, local, 'picker');
64
+ return {
65
+ createSidebar: decorate(createSidebar, (component, local) => enhanceNavigation(component.element, local, 'sidebar')),
66
+ createPagination: decorate(createPagination, (component, local) => enhanceNavigation(component.element, local, 'pagination')),
67
+ createDatePicker: decorate(createDatePicker, picker),
68
+ createTimePicker: decorate(createTimePicker, picker),
69
+ createDateTimePicker: decorate(createDateTimePicker, picker),
70
+ createDateRangePicker: decorate(createDateRangePicker, picker),
71
+ createTreeView: decorate(createTreeView, (component, local) => enhanceTree(component.element, local)),
72
+ createNotification: decorate(createNotification, (component, local) => enhanceNotifications(component.element, local)),
73
+ createButton: decorate(createMotionButton, (component, local) => {
74
+ const button = component.element.querySelector('button')!;
75
+ const press = createPressFeedback(button, local), update = component.update.bind(component);
76
+ component.update = patch => { update(patch); button.classList.add('blu-motion-press'); };
77
+ return press;
78
+ }),
79
+ createModal: decorate(createModal, surface('modal')),
80
+ createDrawer: decorate(createDrawer, surface('drawer')),
81
+ createCommandPalette: decorate(createCommandPalette, surface('palette')),
82
+ createTextInput: decorate(createTextInput, field),
83
+ createTextArea: decorate(createTextArea, field),
84
+ createCheckbox: decorate(createCheckbox, field),
85
+ createSwitch: decorate(createSwitch, field),
86
+ createRadioButton: decorate(createRadioButton, field),
87
+ createRadioGroup: decorate(createRadioGroup, field),
88
+ createFormField: decorate(createFormField, field),
89
+ createSelect: decorate(createSelect, (component, local) => {
90
+ const feedback = field(component, local), menu = observeSurface(component.element, local, 'select');
91
+ return { destroy() { menu.destroy(); feedback.destroy(); } };
92
+ }),
93
+ /** Replace collection defaults. Existing per-component overrides stay in place. */
94
+ configure(settings: ComponentMotionSettings = {}) {
95
+ if (disposed) throw Error('Motion components are destroyed.');
96
+ defaults = validateComponentMotion(settings);
97
+ for (const apply of configurations.values()) apply();
98
+ },
99
+ destroy() { if (disposed) return; disposed = true; for (const component of Array.from(instances).reverse()) component.destroy(); },
100
+ };
101
+ }
@@ -0,0 +1,60 @@
1
+ /** Local opt-in data entry. Existing Table/Grid defaults and native entries are unchanged. */
2
+ import { createTable as baseTable, tableRows } from './form-layout/table.js';
3
+ import type { TableOptions, TableEvents } from './form-layout/types.js';
4
+ import { createGridBasic as baseGrid } from './grid-basic.js';
5
+ import type { GridBasicOptions, GridBasicEvents } from './grid-basic.js';
6
+ import type { MotionController } from './motion/controller.js';
7
+ import { enhanceDataRows } from './web-motion/data-rows.js';
8
+
9
+ import { createComponentMotionScope, type ComponentMotionSettings } from './motion/component-settings.js';
10
+ import { createMotionSettingsGroup } from './motion/settings-group.js';
11
+
12
+ export function createMotionData(motion: MotionController, defaults: ComponentMotionSettings = {}) {
13
+ const settings = createMotionSettingsGroup(defaults);
14
+ const instances = new Set<{ destroy(): void }>(); let dead = false;
15
+ function own<C extends { destroy(): void }>(component: C, extra: { destroy(): void }, scope: ReturnType<typeof createComponentMotionScope>) {
16
+ const binding = settings.bind(scope.update);
17
+ const destroy = component.destroy.bind(component); let disposed = false;
18
+ component.destroy = () => { if (disposed) return; disposed = true; binding.destroy(); extra.destroy(); scope.destroy(); destroy(); instances.delete(component); };
19
+ instances.add(component); return Object.assign(component, {setMotion: binding.setMotion});
20
+ }
21
+ return {
22
+ configure(next: ComponentMotionSettings = {}) { if (dead) throw Error('Motion data components are destroyed.'); settings.configure(next); },
23
+ createTable<Row extends object>(host: HTMLElement, initial: TableOptions<Row>, events: TableEvents<Row> = {}) {
24
+ if (dead) throw Error('Motion data components are destroyed.');
25
+ let options = initial;
26
+ function validate(p: TableOptions<Row>) {
27
+ if (!p.RowKey) throw Error('Animated tables require a stable RowKey.');
28
+ const ids = p.Rows.map(row => row[p.RowKey!]);
29
+ if (ids.some(id => !['string', 'number'].includes(typeof id) || String(id) === '') || new Set(ids.map(String)).size !== ids.length) throw Error('Animated tables require unique, nonempty row keys.');
30
+ }
31
+ validate(options);
32
+ const component = baseTable(host, initial, events), update = component.update;
33
+ const scope = createComponentMotionScope(component.element, motion, settings.settings);
34
+ const extra = enhanceDataRows(component.element, scope.motion, () => {
35
+ const rows = options.Loading ? [] : tableRows({ ...options, ...component.getState() }).rows;
36
+ const elements = [...component.element.querySelectorAll<HTMLTableRowElement>('tbody > tr')];
37
+ return rows.map((row, index) => ({ key: String(row[options.RowKey!]), element: elements[index], cells: [...elements[index].cells], cellKeys: [...options.Columns.map(c => c.Key), '__actions'] }));
38
+ });
39
+ component.update = patch => {
40
+ const next = { ...options, ...patch }; validate(next); extra.capture();
41
+ const focused = document.activeElement instanceof HTMLElement && component.element.contains(document.activeElement) ? document.activeElement : null;
42
+ update(patch); options = next;
43
+ if (focused && !focused.isConnected && document.activeElement === document.body) component.element.querySelector<HTMLElement>('.blu-table__scroll')!.focus({ preventScroll: true });
44
+ };
45
+ return own(component, extra, scope);
46
+ },
47
+ createGridBasic(host: HTMLElement, initial: GridBasicOptions, events: GridBasicEvents = {}) {
48
+ if (dead) throw Error('Motion data components are destroyed.');
49
+ const component = baseGrid(host, initial, events), update = component.update;
50
+ const scope = createComponentMotionScope(component.element, motion, settings.settings);
51
+ const extra = enhanceDataRows(component.element, scope.motion, () => [...component.element.querySelectorAll<HTMLElement>('.ag-center-cols-container > .ag-row[row-id], .ag-grid-scrolling-container > .ag-row[row-id]')].map(element => {
52
+ const cells = [...element.querySelectorAll<HTMLElement>('.ag-cell')];
53
+ return { key: element.getAttribute('row-id')!, element, cells, cellKeys: cells.map(cell => cell.getAttribute('col-id')!) };
54
+ }));
55
+ component.update = patch => { extra.capture(); update(patch); };
56
+ return own(component, extra, scope);
57
+ },
58
+ destroy() { if (dead) return; dead = true; for (const component of [...instances]) component.destroy(); },
59
+ };
60
+ }
@@ -33,7 +33,19 @@ export function createRadioGroup(host: HTMLElement, initial: RadioGroupOptions,
33
33
  let p = { ...initial }, controls: HTMLInputElement[] = [];
34
34
  const f = frame(host, p, true), name = p.Name || uid(); const life = lifecycle(f.root, reset);
35
35
  function update(patch: Partial<RadioGroupOptions>) {
36
- life.check(); const replace = !controls.length || 'Options' in patch; p = { ...p, ...patch };
36
+ life.check();
37
+ // Native bindings resend Options on every value change. Preserve the same
38
+ // inputs when the options are unchanged so keyboard and screen-reader focus
39
+ // stay on the selected choice.
40
+ const replace = !controls.length || (!!patch.Options && (
41
+ patch.Options.length !== p.Options.length || patch.Options.some((option, i) => {
42
+ const previous = p.Options[i];
43
+ return option.Value !== previous.Value || option.Label !== previous.Label ||
44
+ option.Description !== previous.Description || option.Disabled !== previous.Disabled;
45
+ })
46
+ ));
47
+ const focusedValue = controls.find(input => input === document.activeElement)?.value;
48
+ p = { ...p, ...patch };
37
49
  if (new Set(p.Options.map(o => o.Value)).size !== p.Options.length) throw Error('Radio option values must be unique.');
38
50
  f.body.className = 'blu-radio-group blu-radio-group--' + (p.Direction || 'Vertical').toLowerCase();
39
51
  if (replace) {
@@ -58,6 +70,10 @@ export function createRadioGroup(host: HTMLElement, initial: RadioGroupOptions,
58
70
  attr(input, 'aria-readonly', p.ReadOnly ? 'true' : undefined);
59
71
  if (p.Options[i].Description) attr(input, 'aria-describedby', [input.getAttribute('aria-describedby'), input.id + '-description'].filter(Boolean).join(' '));
60
72
  });
73
+ if (replace && focusedValue !== undefined) {
74
+ (controls.find(input => input.value === focusedValue && !input.disabled) ||
75
+ controls.find(input => input.checked && !input.disabled) || controls.find(input => !input.disabled))?.focus();
76
+ }
61
77
  }
62
78
  function reset() { update({ Value: initial.Value || '' }); } update({});
63
79
  return { element: f.root, update, reset, getValue: () => controls.find(c => c.checked)?.value || '',
@@ -0,0 +1,38 @@
1
+ import type { MotionController } from './controller.js';
2
+
3
+ export interface ActionMessages { pending: string; success: string; error: string }
4
+ export type ActionResult = 'success' | 'error' | 'busy' | 'cancelled';
5
+
6
+ /** Feedback follows an actual action promise. No simulated success, timed reset,
7
+ * duplicated live region or overlapping action. The caller owns data and controls. */
8
+ export function createActionFeedback(status: HTMLElement, motion: MotionController) {
9
+ const original = { role: status.getAttribute('role'), atomic: status.getAttribute('aria-atomic'), state: status.getAttribute('data-action-state') };
10
+ status.setAttribute('role', 'status'); status.setAttribute('aria-atomic', 'true');
11
+ let dead = false, pending = false;
12
+ const show = (text: string, state: string) => {
13
+ status.textContent = text; status.dataset.actionState = state;
14
+ void motion.play(status, 'fade', { duration: 350, easing: 'ease-out' });
15
+ };
16
+ return {
17
+ get pending() { return pending; },
18
+ async run(action: () => unknown | Promise<unknown>, messages: ActionMessages): Promise<ActionResult> {
19
+ if (dead) return 'cancelled';
20
+ if (pending) return 'busy';
21
+ pending = true; show(messages.pending, 'pending');
22
+ try {
23
+ await action();
24
+ if (dead) return 'cancelled';
25
+ show(messages.success, 'success'); return 'success';
26
+ } catch {
27
+ if (dead) return 'cancelled';
28
+ show(messages.error, 'error'); return 'error';
29
+ } finally { pending = false; }
30
+ },
31
+ destroy() {
32
+ if (dead) return; dead = true; motion.cancel(status);
33
+ for (const [key, value] of [['role', original.role], ['aria-atomic', original.atomic], ['data-action-state', original.state]]) {
34
+ if (value === null) status.removeAttribute(key!); else status.setAttribute(key!, value!);
35
+ }
36
+ },
37
+ };
38
+ }
@@ -0,0 +1,41 @@
1
+ import { preserveStyles } from './structure.js';
2
+
3
+ export interface BorderGlowOptions {
4
+ /** One slow circuit in milliseconds, 4–60 seconds. Default: 12 seconds. */
5
+ duration?: number;
6
+ paused?: boolean;
7
+ }
8
+ const owners = new WeakSet<HTMLElement>();
9
+ function duration(value: number = 12000) {
10
+ if (!Number.isFinite(value) || value < 4000 || value > 60000) throw Error('Border glow duration must be from 4000 to 60000 ms.');
11
+ return value;
12
+ }
13
+ /** Decorative, noninteractive border only. CSS honors the motion scope and OS.
14
+ * Pause automatically while outside the viewport or in a hidden document. */
15
+ export function createBorderGlow(element: HTMLElement, options: BorderGlowOptions = {}) {
16
+ let time = duration(options.duration), paused = options.paused ?? false, visible = true, dead = false;
17
+ if (owners.has(element)) throw Error('This element already has a border glow.');
18
+ const doc = element.ownerDocument, win = doc.defaultView;
19
+ if (!win || !element.isConnected) throw Error('Mount a border glow on a connected browser element.');
20
+ const restore = preserveStyles(element, ['position']);
21
+ if (win.getComputedStyle(element).position === 'static') element.style.position = 'relative';
22
+ const overlay = doc.createElement('span'), rim = doc.createElement('span'), beam = doc.createElement('span');
23
+ overlay.className = 'blu-motion-border-glow'; rim.className = 'blu-motion-border-glow__rim';
24
+ beam.className = 'blu-motion-border-glow__beam'; rim.append(beam);
25
+ overlay.setAttribute('aria-hidden', 'true'); overlay.inert = true; overlay.append(rim); element.append(overlay); owners.add(element);
26
+ const sync = () => { overlay.style.setProperty('--blu-glow-duration', time + 'ms'); overlay.dataset.paused = String(paused || !visible || doc.hidden); };
27
+ const measure = () => overlay.style.setProperty('--blu-glow-size', Math.ceil(Math.hypot(element.offsetWidth + 4, element.offsetHeight + 4)) + 'px');
28
+ const resize = new ResizeObserver(measure); resize.observe(element); measure();
29
+ const intersection = typeof IntersectionObserver === 'function' ? new IntersectionObserver(entries => { visible = entries[0].isIntersecting; sync(); }) : null;
30
+ intersection?.observe(element); doc.addEventListener('visibilitychange', sync); sync();
31
+ return {
32
+ element: overlay,
33
+ update(next: BorderGlowOptions) {
34
+ if (dead) throw Error('Border glow is destroyed.');
35
+ if (next.duration !== undefined) time = duration(next.duration);
36
+ if (next.paused !== undefined) paused = next.paused;
37
+ sync();
38
+ },
39
+ destroy() { if (dead) return; dead = true; resize.disconnect(); intersection?.disconnect(); doc.removeEventListener('visibilitychange', sync); overlay.remove(); restore(); owners.delete(element); },
40
+ };
41
+ }