@yewdiel21/motion-kit 0.0.0-stage → 0.9.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/NOTICE +38 -0
- package/README.md +80 -2
- package/bin/motion-kit.mjs +242 -0
- package/css.d.ts +2 -0
- package/dist/auto/index.d.ts +63 -0
- package/dist/auto/index.js +207 -0
- package/dist/auto/index.js.map +1 -0
- package/dist/chunk-2BQGFOA7.js +177 -0
- package/dist/chunk-2BQGFOA7.js.map +1 -0
- package/dist/chunk-34XYOXHG.js +2374 -0
- package/dist/chunk-34XYOXHG.js.map +1 -0
- package/dist/chunk-6OJAP6ZL.js +832 -0
- package/dist/chunk-6OJAP6ZL.js.map +1 -0
- package/dist/chunk-ABNRDDO7.js +28 -0
- package/dist/chunk-ABNRDDO7.js.map +1 -0
- package/dist/chunk-FS2HJVVY.js +3320 -0
- package/dist/chunk-FS2HJVVY.js.map +1 -0
- package/dist/chunk-J27KH2A2.js +157 -0
- package/dist/chunk-J27KH2A2.js.map +1 -0
- package/dist/chunk-KLY5BRRI.js +18 -0
- package/dist/chunk-KLY5BRRI.js.map +1 -0
- package/dist/chunk-Q4LVFNLN.js +1617 -0
- package/dist/chunk-Q4LVFNLN.js.map +1 -0
- package/dist/chunk-RFDIZYFX.js +304 -0
- package/dist/chunk-RFDIZYFX.js.map +1 -0
- package/dist/chunk-TGV27ZWM.js +17 -0
- package/dist/chunk-TGV27ZWM.js.map +1 -0
- package/dist/confetti-BSX1CLLV.d.ts +41 -0
- package/dist/css/components/chat-background.css +87 -0
- package/dist/css/components/chat.css +237 -0
- package/dist/css/components/cursor.css +178 -0
- package/dist/css/components/dialog.css +259 -0
- package/dist/css/components/island.css +301 -0
- package/dist/css/components/menu.css +105 -0
- package/dist/css/components/nav.css +1054 -0
- package/dist/css/components/steps.css +50 -0
- package/dist/css/components/theme-picker.css +115 -0
- package/dist/css/components/toast.css +115 -0
- package/dist/css/controls.css +950 -0
- package/dist/css/effects.css +587 -0
- package/dist/css/fonts.css +3 -0
- package/dist/css/themes/black-green.css +126 -0
- package/dist/css/themes/black.css +126 -0
- package/dist/css/themes/blue.css +126 -0
- package/dist/css/themes/brown.css +126 -0
- package/dist/css/themes/cold-blue.css +126 -0
- package/dist/css/themes/funny.css +126 -0
- package/dist/css/themes/gray.css +126 -0
- package/dist/css/themes/green.css +126 -0
- package/dist/css/themes/modern-1.css +126 -0
- package/dist/css/themes/neogreen.css +126 -0
- package/dist/css/themes/new.css +126 -0
- package/dist/css/themes/new2.css +126 -0
- package/dist/css/themes/oled.css +126 -0
- package/dist/css/themes/orange.css +126 -0
- package/dist/css/themes/pastel-pink.css +126 -0
- package/dist/css/themes/pink.css +126 -0
- package/dist/css/themes/purple.css +126 -0
- package/dist/css/themes/red.css +126 -0
- package/dist/css/themes/super-blue.css +126 -0
- package/dist/css/themes.css +2431 -0
- package/dist/css/tokens.css +452 -0
- package/dist/elements/index.d.ts +183 -0
- package/dist/elements/index.js +472 -0
- package/dist/elements/index.js.map +1 -0
- package/dist/engine-CUr9QijM.d.ts +46 -0
- package/dist/gl/index.d.ts +61 -0
- package/dist/gl/index.js +18 -0
- package/dist/gl/index.js.map +1 -0
- package/dist/index.css +5296 -0
- package/dist/index.d.ts +440 -0
- package/dist/index.js +390 -0
- package/dist/index.js.map +1 -0
- package/dist/react/index.d.ts +476 -0
- package/dist/react/index.js +969 -0
- package/dist/react/index.js.map +1 -0
- package/dist/reduced-BM4Ls0g5.d.ts +566 -0
- package/dist/reveal-WFUWDrei.d.ts +416 -0
- package/dist/store-Bqy_obnD.d.ts +195 -0
- package/dist/theme/generate.d.ts +18 -0
- package/dist/theme/generate.js +64 -0
- package/dist/theme/generate.js.map +1 -0
- package/dist/theme/index.d.ts +17 -0
- package/dist/theme/index.js +32 -0
- package/dist/theme/index.js.map +1 -0
- package/dist/theme-BFMoCzi6.d.ts +158 -0
- package/dist/types-BkS_raL8.d.ts +7 -0
- package/package.json +114 -3
- package/registry.json +469 -0
- package/skills/motion-kit/SKILL.md +189 -0
- package/skills/motion-kit/reference/components.md +86 -0
- package/skills/motion-kit/reference/design.md +92 -0
- package/skills/motion-kit/reference/recipes.md +172 -0
- package/src/auto/auto-init.ts +151 -0
- package/src/auto/index.ts +3 -0
- package/src/auto/parse-options.ts +68 -0
- package/src/components/chat/README.md +90 -0
- package/src/components/chat/chat.css +236 -0
- package/src/components/chat/element.ts +98 -0
- package/src/components/chat/engine.ts +295 -0
- package/src/components/chat/react.tsx +196 -0
- package/src/components/chat-background/README.md +50 -0
- package/src/components/chat-background/audio.ts +56 -0
- package/src/components/chat-background/chat-background.css +86 -0
- package/src/components/chat-background/element.ts +31 -0
- package/src/components/chat-background/engine.ts +108 -0
- package/src/components/chat-background/react.tsx +36 -0
- package/src/components/cursor/README.md +53 -0
- package/src/components/cursor/cursor.css +177 -0
- package/src/components/cursor/element.ts +23 -0
- package/src/components/cursor/engine.ts +254 -0
- package/src/components/cursor/react.tsx +26 -0
- package/src/components/dialog/README.md +106 -0
- package/src/components/dialog/dialog.css +258 -0
- package/src/components/dialog/element.ts +118 -0
- package/src/components/dialog/engine.ts +432 -0
- package/src/components/dialog/react.tsx +200 -0
- package/src/components/icon.tsx +10 -0
- package/src/components/icons.ts +24 -0
- package/src/components/island/README.md +91 -0
- package/src/components/island/element.ts +45 -0
- package/src/components/island/engine.ts +200 -0
- package/src/components/island/island.css +300 -0
- package/src/components/island/react.tsx +115 -0
- package/src/components/menu/README.md +67 -0
- package/src/components/menu/element.ts +55 -0
- package/src/components/menu/engine.ts +142 -0
- package/src/components/menu/menu.css +104 -0
- package/src/components/menu/react.tsx +82 -0
- package/src/components/nav/README.md +85 -0
- package/src/components/nav/element.ts +39 -0
- package/src/components/nav/engine.ts +164 -0
- package/src/components/nav/nav.css +1053 -0
- package/src/components/nav/react.tsx +90 -0
- package/src/components/steps/README.md +70 -0
- package/src/components/steps/element.ts +43 -0
- package/src/components/steps/engine.ts +185 -0
- package/src/components/steps/react.tsx +91 -0
- package/src/components/steps/steps.css +49 -0
- package/src/components/theme-picker/README.md +57 -0
- package/src/components/theme-picker/element.ts +20 -0
- package/src/components/theme-picker/engine.ts +91 -0
- package/src/components/theme-picker/react.tsx +31 -0
- package/src/components/theme-picker/theme-picker.css +114 -0
- package/src/components/toast/README.md +60 -0
- package/src/components/toast/element.ts +25 -0
- package/src/components/toast/engine.ts +483 -0
- package/src/components/toast/react.tsx +22 -0
- package/src/components/toast/store.ts +122 -0
- package/src/components/toast/toast.css +114 -0
- package/src/core/channels.ts +165 -0
- package/src/core/curves.ts +35 -0
- package/src/core/frame.ts +12 -0
- package/src/core/gsap.ts +65 -0
- package/src/core/loop.ts +44 -0
- package/src/core/math.ts +9 -0
- package/src/core/physics-spring.ts +59 -0
- package/src/core/pointer.ts +63 -0
- package/src/core/reduced.ts +21 -0
- package/src/core/scope.ts +70 -0
- package/src/core/scroll-track.ts +106 -0
- package/src/core/split.ts +68 -0
- package/src/core/spring.ts +22 -0
- package/src/core/styles.ts +31 -0
- package/src/core/timing.ts +204 -0
- package/src/core/types.ts +20 -0
- package/src/core/visible.ts +19 -0
- package/src/css/base.css +142 -0
- package/src/css/controls/README.md +122 -0
- package/src/css/controls/burger.css +67 -0
- package/src/css/controls/button.css +226 -0
- package/src/css/controls/checkbox.css +53 -0
- package/src/css/controls/chip.css +85 -0
- package/src/css/controls/field.css +124 -0
- package/src/css/controls/glass.css +220 -0
- package/src/css/controls/icon.css +26 -0
- package/src/css/controls/segmented.css +60 -0
- package/src/css/controls/switch.css +72 -0
- package/src/css/default-theme.css +123 -0
- package/src/css/effects/README.md +134 -0
- package/src/css/effects/anim.css +224 -0
- package/src/css/effects/fade.css +15 -0
- package/src/css/effects/float.css +64 -0
- package/src/css/effects/frost.css +62 -0
- package/src/css/effects/grain.css +58 -0
- package/src/css/effects/loading.css +60 -0
- package/src/css/effects/success.css +22 -0
- package/src/css/effects/text.css +67 -0
- package/src/css/fonts.css +3 -0
- package/src/css/states.css +51 -0
- package/src/css/themes/black-green.css +126 -0
- package/src/css/themes/black.css +126 -0
- package/src/css/themes/blue.css +126 -0
- package/src/css/themes/brown.css +126 -0
- package/src/css/themes/cold-blue.css +126 -0
- package/src/css/themes/funny.css +126 -0
- package/src/css/themes/gray.css +126 -0
- package/src/css/themes/green.css +126 -0
- package/src/css/themes/modern-1.css +126 -0
- package/src/css/themes/neogreen.css +126 -0
- package/src/css/themes/new.css +126 -0
- package/src/css/themes/new2.css +126 -0
- package/src/css/themes/oled.css +126 -0
- package/src/css/themes/orange.css +126 -0
- package/src/css/themes/pastel-pink.css +126 -0
- package/src/css/themes/pink.css +126 -0
- package/src/css/themes/purple.css +126 -0
- package/src/css/themes/red.css +126 -0
- package/src/css/themes/super-blue.css +126 -0
- package/src/css/tokens.css +182 -0
- package/src/elements/base.ts +76 -0
- package/src/elements/index.ts +48 -0
- package/src/gl/index.ts +15 -0
- package/src/gl/liquid-blend/README.md +23 -0
- package/src/gl/liquid-blend/create-liquid-blend.ts +7 -0
- package/src/gl/liquid-blend/shader.ts +9 -0
- package/src/gl/matcap/README.md +23 -0
- package/src/gl/matcap/create-matcap.ts +7 -0
- package/src/gl/matcap/shader.ts +16 -0
- package/src/gl/particle-hero/README.md +41 -0
- package/src/gl/particle-hero/create-particle-hero.ts +544 -0
- package/src/gl/particle-hero/particle-hero.css +19 -0
- package/src/gl/particle-hero/shaders.ts +84 -0
- package/src/gl/shader-blur/README.md +23 -0
- package/src/gl/shader-blur/create-shader-blur.ts +7 -0
- package/src/gl/shader-blur/shader.ts +11 -0
- package/src/gl/shader-grain/README.md +23 -0
- package/src/gl/shader-grain/create-shader-grain.ts +7 -0
- package/src/gl/shader-grain/shader.ts +6 -0
- package/src/gl/shared/create-shader-effect.ts +186 -0
- package/src/gl/shared/shader.css +3 -0
- package/src/gl/shared/shaders.ts +30 -0
- package/src/index.ts +177 -0
- package/src/motion/anchor.ts +45 -0
- package/src/motion/chat.ts +97 -0
- package/src/motion/confetti.ts +130 -0
- package/src/motion/count.ts +95 -0
- package/src/motion/details.ts +70 -0
- package/src/motion/drag-dismiss.ts +231 -0
- package/src/motion/effects.ts +81 -0
- package/src/motion/entrances.ts +27 -0
- package/src/motion/floating.ts +389 -0
- package/src/motion/flow.ts +115 -0
- package/src/motion/indicator.ts +44 -0
- package/src/motion/layout.ts +39 -0
- package/src/motion/liquid.ts +227 -0
- package/src/motion/morph.ts +540 -0
- package/src/motion/reveal.ts +112 -0
- package/src/motion/sheet.ts +65 -0
- package/src/motion/surface.ts +108 -0
- package/src/react/animated-number.tsx +30 -0
- package/src/react/flip-group.tsx +44 -0
- package/src/react/frost.tsx +33 -0
- package/src/react/index.ts +80 -0
- package/src/react/slot-text.tsx +44 -0
- package/src/react/theme-provider.tsx +42 -0
- package/src/react/use-card-dock.ts +9 -0
- package/src/react/use-cascade.ts +9 -0
- package/src/react/use-contrast.ts +9 -0
- package/src/react/use-counter.ts +8 -0
- package/src/react/use-curved-carousel.ts +10 -0
- package/src/react/use-depth-grid.ts +9 -0
- package/src/react/use-details.ts +7 -0
- package/src/react/use-dock-pill.ts +10 -0
- package/src/react/use-drag-ruler.ts +10 -0
- package/src/react/use-fill-text.ts +9 -0
- package/src/react/use-fit-text.ts +8 -0
- package/src/react/use-flow.ts +8 -0
- package/src/react/use-frame-zoom.ts +9 -0
- package/src/react/use-infinite-reel.ts +8 -0
- package/src/react/use-letters.ts +9 -0
- package/src/react/use-liquid-blend.ts +8 -0
- package/src/react/use-marquee.ts +9 -0
- package/src/react/use-mask-text.ts +9 -0
- package/src/react/use-matcap.ts +8 -0
- package/src/react/use-parallax.ts +9 -0
- package/src/react/use-particle-hero.ts +9 -0
- package/src/react/use-pile.ts +8 -0
- package/src/react/use-pop.ts +8 -0
- package/src/react/use-radial-menu.ts +10 -0
- package/src/react/use-recipe.ts +38 -0
- package/src/react/use-reflow.ts +9 -0
- package/src/react/use-repel-bento.ts +8 -0
- package/src/react/use-reveal.ts +8 -0
- package/src/react/use-rotator.ts +10 -0
- package/src/react/use-shader-blur.ts +8 -0
- package/src/react/use-shader-grain.ts +8 -0
- package/src/react/use-slot-text.ts +17 -0
- package/src/react/use-snap-strip.ts +9 -0
- package/src/react/use-sortable.ts +14 -0
- package/src/react/use-spring-cards.ts +8 -0
- package/src/react/use-stretch-footer.ts +20 -0
- package/src/react/use-tabs.ts +10 -0
- package/src/react/use-text-roll.ts +9 -0
- package/src/react/use-ticket-menu.ts +15 -0
- package/src/react/use-words.ts +8 -0
- package/src/recipes/card-dock/README.md +36 -0
- package/src/recipes/card-dock/card-dock.css +178 -0
- package/src/recipes/card-dock/create-card-dock.ts +97 -0
- package/src/recipes/cascade/README.md +33 -0
- package/src/recipes/cascade/create-cascade.ts +111 -0
- package/src/recipes/contrast/README.md +29 -0
- package/src/recipes/contrast/create-contrast.ts +68 -0
- package/src/recipes/counter/README.md +42 -0
- package/src/recipes/counter/create-counter.ts +84 -0
- package/src/recipes/curved-carousel/README.md +50 -0
- package/src/recipes/curved-carousel/carousel-math.ts +20 -0
- package/src/recipes/curved-carousel/create-curved-carousel.ts +257 -0
- package/src/recipes/curved-carousel/curved-carousel.css +50 -0
- package/src/recipes/depth-grid/README.md +30 -0
- package/src/recipes/depth-grid/create-depth-grid.ts +120 -0
- package/src/recipes/details/README.md +31 -0
- package/src/recipes/details/create-details.ts +27 -0
- package/src/recipes/dock-pill/README.md +23 -0
- package/src/recipes/dock-pill/create-dock-pill.ts +68 -0
- package/src/recipes/dock-pill/dock-pill.css +5 -0
- package/src/recipes/drag-ruler/README.md +21 -0
- package/src/recipes/drag-ruler/create-drag-ruler.ts +95 -0
- package/src/recipes/drag-ruler/drag-ruler.css +16 -0
- package/src/recipes/fill-text/README.md +27 -0
- package/src/recipes/fill-text/create-fill-text.ts +93 -0
- package/src/recipes/fit-text/README.md +37 -0
- package/src/recipes/fit-text/create-fit-text.ts +55 -0
- package/src/recipes/fit-text/fit-text.css +6 -0
- package/src/recipes/flow/README.md +46 -0
- package/src/recipes/flow/create-flow.ts +55 -0
- package/src/recipes/frame-zoom/README.md +29 -0
- package/src/recipes/frame-zoom/create-frame-zoom.ts +91 -0
- package/src/recipes/infinite-reel/README.md +23 -0
- package/src/recipes/infinite-reel/create-infinite-reel.ts +140 -0
- package/src/recipes/infinite-reel/infinite-reel.css +8 -0
- package/src/recipes/letters/README.md +30 -0
- package/src/recipes/letters/create-letters.ts +113 -0
- package/src/recipes/marquee/README.md +32 -0
- package/src/recipes/marquee/create-marquee.ts +74 -0
- package/src/recipes/marquee/marquee.css +41 -0
- package/src/recipes/mask-text/README.md +39 -0
- package/src/recipes/mask-text/create-mask-text.ts +134 -0
- package/src/recipes/parallax/README.md +31 -0
- package/src/recipes/parallax/create-parallax.ts +74 -0
- package/src/recipes/pile/README.md +48 -0
- package/src/recipes/pile/create-pile.ts +50 -0
- package/src/recipes/pile/pile-math.ts +27 -0
- package/src/recipes/pile/pile.css +63 -0
- package/src/recipes/pill/README.md +30 -0
- package/src/recipes/pill/pill.css +48 -0
- package/src/recipes/pop/README.md +36 -0
- package/src/recipes/pop/create-pop.ts +52 -0
- package/src/recipes/radial-menu/README.md +44 -0
- package/src/recipes/radial-menu/create-radial-menu.ts +96 -0
- package/src/recipes/radial-menu/radial-menu.css +45 -0
- package/src/recipes/reflow/README.md +47 -0
- package/src/recipes/reflow/create-reflow.ts +49 -0
- package/src/recipes/repel-bento/README.md +21 -0
- package/src/recipes/repel-bento/create-repel-bento.ts +65 -0
- package/src/recipes/repel-bento/repel-bento.css +3 -0
- package/src/recipes/reveal/README.md +36 -0
- package/src/recipes/reveal/create-reveal.ts +48 -0
- package/src/recipes/rotator/README.md +21 -0
- package/src/recipes/rotator/create-rotator.ts +66 -0
- package/src/recipes/rotator/rotator.css +3 -0
- package/src/recipes/slot-text/README.md +44 -0
- package/src/recipes/slot-text/create-slot-text.ts +244 -0
- package/src/recipes/slot-text/slot-text.css +42 -0
- package/src/recipes/snap-strip/README.md +31 -0
- package/src/recipes/snap-strip/create-snap-strip.ts +133 -0
- package/src/recipes/snap-strip/snap-strip.css +23 -0
- package/src/recipes/sortable/README.md +88 -0
- package/src/recipes/sortable/create-sortable.ts +863 -0
- package/src/recipes/sortable/sortable.css +40 -0
- package/src/recipes/spring-cards/README.md +48 -0
- package/src/recipes/spring-cards/create-spring-cards.ts +140 -0
- package/src/recipes/spring-cards/spring-cards.css +23 -0
- package/src/recipes/stretch-footer/README.md +44 -0
- package/src/recipes/stretch-footer/create-stretch-footer.ts +69 -0
- package/src/recipes/stretch-footer/stretch-footer.css +28 -0
- package/src/recipes/tabs/README.md +39 -0
- package/src/recipes/tabs/create-tabs.ts +91 -0
- package/src/recipes/tabs/tabs.css +19 -0
- package/src/recipes/text-roll/README.md +35 -0
- package/src/recipes/text-roll/create-text-roll.ts +137 -0
- package/src/recipes/text-roll/text-roll.css +21 -0
- package/src/recipes/ticket-menu/README.md +24 -0
- package/src/recipes/ticket-menu/create-ticket-menu.ts +6 -0
- package/src/recipes/words/README.md +43 -0
- package/src/recipes/words/create-words.ts +114 -0
- package/src/theme/css-format.ts +32 -0
- package/src/theme/generate.ts +72 -0
- package/src/theme/index.ts +15 -0
- package/src/theme/presets.ts +79 -0
- package/src/theme/theme.ts +119 -0
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { ScrollTrigger } from '../core/gsap';
|
|
2
|
+
import { warnOnce, type Instance } from '../core/types';
|
|
3
|
+
import { createSpringCards } from '../recipes/spring-cards/create-spring-cards';
|
|
4
|
+
import { createCurvedCarousel } from '../recipes/curved-carousel/create-curved-carousel';
|
|
5
|
+
import { createPile } from '../recipes/pile/create-pile';
|
|
6
|
+
import { createWords } from '../recipes/words/create-words';
|
|
7
|
+
import { createFlow } from '../recipes/flow/create-flow';
|
|
8
|
+
import { createPop } from '../recipes/pop/create-pop';
|
|
9
|
+
import { createFitText } from '../recipes/fit-text/create-fit-text';
|
|
10
|
+
import { createStretchFooter } from '../recipes/stretch-footer/create-stretch-footer';
|
|
11
|
+
import { createRadialMenu } from '../recipes/radial-menu/create-radial-menu';
|
|
12
|
+
import { createTabs } from '../recipes/tabs/create-tabs';
|
|
13
|
+
import { createCounter } from '../recipes/counter/create-counter';
|
|
14
|
+
import { createDetails } from '../recipes/details/create-details';
|
|
15
|
+
import { createReveal } from '../recipes/reveal/create-reveal';
|
|
16
|
+
import { createReflow } from '../recipes/reflow/create-reflow';
|
|
17
|
+
import { createSlotText } from '../recipes/slot-text/create-slot-text';
|
|
18
|
+
import { createSortable } from '../recipes/sortable/create-sortable';
|
|
19
|
+
import { createMaskText } from '../recipes/mask-text/create-mask-text';
|
|
20
|
+
import { createCascade } from '../recipes/cascade/create-cascade';
|
|
21
|
+
import { createTextRoll } from '../recipes/text-roll/create-text-roll';
|
|
22
|
+
import { createLetters } from '../recipes/letters/create-letters';
|
|
23
|
+
import { createParallax } from '../recipes/parallax/create-parallax';
|
|
24
|
+
import { createDepthGrid } from '../recipes/depth-grid/create-depth-grid';
|
|
25
|
+
import { createFrameZoom } from '../recipes/frame-zoom/create-frame-zoom';
|
|
26
|
+
import { createContrast } from '../recipes/contrast/create-contrast';
|
|
27
|
+
import { createMarquee } from '../recipes/marquee/create-marquee';
|
|
28
|
+
import { createCardDock } from '../recipes/card-dock/create-card-dock';
|
|
29
|
+
import { createSnapStrip } from '../recipes/snap-strip/create-snap-strip';
|
|
30
|
+
import { createFillText } from '../recipes/fill-text/create-fill-text';
|
|
31
|
+
import { parseOptions } from './parse-options';
|
|
32
|
+
import { createTicketMenu } from '../recipes/ticket-menu/create-ticket-menu';
|
|
33
|
+
import { createInfiniteReel } from '../recipes/infinite-reel/create-infinite-reel';
|
|
34
|
+
import { createRepelBento } from '../recipes/repel-bento/create-repel-bento';
|
|
35
|
+
import { createRotator } from '../recipes/rotator/create-rotator';
|
|
36
|
+
import { createDragRuler } from '../recipes/drag-ruler/create-drag-ruler';
|
|
37
|
+
import { createDockPill } from '../recipes/dock-pill/create-dock-pill';
|
|
38
|
+
|
|
39
|
+
type Factory = (el: HTMLElement, options: Record<string, unknown>) => Instance;
|
|
40
|
+
|
|
41
|
+
const factories = {
|
|
42
|
+
'spring-cards': createSpringCards,
|
|
43
|
+
'curved-carousel': (el, options) =>
|
|
44
|
+
createCurvedCarousel(el, {
|
|
45
|
+
...options,
|
|
46
|
+
onSelect: (index, slide) => el.dispatchEvent(new CustomEvent('mk:select', { detail: { index, slide }, bubbles: true })),
|
|
47
|
+
}),
|
|
48
|
+
pile: createPile,
|
|
49
|
+
words: createWords,
|
|
50
|
+
flow: createFlow,
|
|
51
|
+
pop: createPop,
|
|
52
|
+
'fit-text': createFitText,
|
|
53
|
+
'stretch-footer': createStretchFooter,
|
|
54
|
+
'radial-menu': createRadialMenu,
|
|
55
|
+
tabs: createTabs,
|
|
56
|
+
counter: createCounter,
|
|
57
|
+
details: (el) => createDetails(el),
|
|
58
|
+
reveal: createReveal,
|
|
59
|
+
reflow: createReflow,
|
|
60
|
+
'slot-text': createSlotText,
|
|
61
|
+
sortable: createSortable,
|
|
62
|
+
'mask-text': createMaskText,
|
|
63
|
+
cascade: createCascade,
|
|
64
|
+
'text-roll': createTextRoll,
|
|
65
|
+
letters: createLetters,
|
|
66
|
+
parallax: createParallax,
|
|
67
|
+
'depth-grid': createDepthGrid,
|
|
68
|
+
'frame-zoom': createFrameZoom,
|
|
69
|
+
contrast: createContrast,
|
|
70
|
+
marquee: createMarquee,
|
|
71
|
+
'card-dock': createCardDock,
|
|
72
|
+
'snap-strip': createSnapStrip,
|
|
73
|
+
'fill-text': createFillText,
|
|
74
|
+
'ticket-menu': createTicketMenu,
|
|
75
|
+
'infinite-reel': createInfiniteReel,
|
|
76
|
+
'repel-bento': createRepelBento,
|
|
77
|
+
'rotator': createRotator,
|
|
78
|
+
'drag-ruler': createDragRuler,
|
|
79
|
+
'dock-pill': createDockPill,
|
|
80
|
+
} satisfies Record<string, Factory>;
|
|
81
|
+
|
|
82
|
+
export type RecipeName = keyof typeof factories;
|
|
83
|
+
export const recipeNames = Object.keys(factories) as RecipeName[];
|
|
84
|
+
|
|
85
|
+
/** Measured text: wait for web fonts, fit before splitting into words. */
|
|
86
|
+
const AFTER_FONTS: RecipeName[] = ['fit-text', 'words', 'slot-text'];
|
|
87
|
+
|
|
88
|
+
const instances = new WeakMap<HTMLElement, Map<RecipeName, Instance>>();
|
|
89
|
+
|
|
90
|
+
export interface AutoInitOptions {
|
|
91
|
+
/** Only start these recipes. */
|
|
92
|
+
only?: RecipeName[];
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Start every `[data-mk]` recipe inside `root` (an element may list several:
|
|
97
|
+
* `data-mk="fit-text words"`). Options come from `data-mk-*` attributes.
|
|
98
|
+
* Returns a function that destroys what this call created. Safe to call again:
|
|
99
|
+
* elements that already run a recipe are skipped.
|
|
100
|
+
*/
|
|
101
|
+
export function autoInit(root: ParentNode = document, { only }: AutoInitOptions = {}): () => void {
|
|
102
|
+
if (typeof window === 'undefined') return () => {};
|
|
103
|
+
const created: Array<[HTMLElement, RecipeName, Instance]> = [];
|
|
104
|
+
let cancelled = false;
|
|
105
|
+
|
|
106
|
+
const elements = [...root.querySelectorAll<HTMLElement>('[data-mk]')];
|
|
107
|
+
if (root instanceof HTMLElement && root.matches('[data-mk]')) elements.unshift(root);
|
|
108
|
+
|
|
109
|
+
const start = (el: HTMLElement, name: RecipeName) => {
|
|
110
|
+
if (cancelled) return;
|
|
111
|
+
const running = instances.get(el) ?? new Map<RecipeName, Instance>();
|
|
112
|
+
if (running.has(name)) return;
|
|
113
|
+
const instance = (factories[name] as Factory)(el, parseOptions(el));
|
|
114
|
+
running.set(name, instance);
|
|
115
|
+
instances.set(el, running);
|
|
116
|
+
created.push([el, name, instance]);
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
const now: Array<[HTMLElement, RecipeName]> = [];
|
|
120
|
+
const later: Array<[HTMLElement, RecipeName]> = [];
|
|
121
|
+
for (const el of elements) {
|
|
122
|
+
for (const raw of (el.dataset.mk ?? '').split(/\s+/).filter(Boolean)) {
|
|
123
|
+
if (!(raw in factories)) {
|
|
124
|
+
warnOnce(`autoInit: unknown recipe "${raw}" (known: ${recipeNames.join(', ')})`);
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
const name = raw as RecipeName;
|
|
128
|
+
if (only && !only.includes(name)) continue;
|
|
129
|
+
(AFTER_FONTS.includes(name) ? later : now).push([el, name]);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
now.forEach(([el, name]) => start(el, name));
|
|
134
|
+
if (later.length) {
|
|
135
|
+
later.sort((a, b) => AFTER_FONTS.indexOf(a[1]) - AFTER_FONTS.indexOf(b[1]));
|
|
136
|
+
const fonts = document.fonts?.ready ?? Promise.resolve();
|
|
137
|
+
void Promise.race([fonts, new Promise((resolve) => setTimeout(resolve, 1500))]).then(() => {
|
|
138
|
+
later.forEach(([el, name]) => start(el, name));
|
|
139
|
+
if (!cancelled) ScrollTrigger.refresh();
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return () => {
|
|
144
|
+
cancelled = true;
|
|
145
|
+
for (const [el, name, instance] of created.reverse()) {
|
|
146
|
+
instance.destroy();
|
|
147
|
+
instances.get(el)?.delete(name);
|
|
148
|
+
}
|
|
149
|
+
created.length = 0;
|
|
150
|
+
};
|
|
151
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/** Attributes the recipes write themselves or use for children; never options. */
|
|
2
|
+
const RESERVED = new Set([
|
|
3
|
+
'',
|
|
4
|
+
'ready',
|
|
5
|
+
'inview',
|
|
6
|
+
'carousel',
|
|
7
|
+
'open',
|
|
8
|
+
'card',
|
|
9
|
+
'slide',
|
|
10
|
+
'toggle',
|
|
11
|
+
'item',
|
|
12
|
+
'phantom',
|
|
13
|
+
'footerTitle',
|
|
14
|
+
'tab',
|
|
15
|
+
'indicator',
|
|
16
|
+
'chevron',
|
|
17
|
+
'text',
|
|
18
|
+
'role',
|
|
19
|
+
'motion',
|
|
20
|
+
'veil',
|
|
21
|
+
'swatch',
|
|
22
|
+
'part',
|
|
23
|
+
'sorting',
|
|
24
|
+
'sortableGroup',
|
|
25
|
+
'sortableLocked',
|
|
26
|
+
'cascade',
|
|
27
|
+
'on',
|
|
28
|
+
'tone',
|
|
29
|
+
'cursorZone',
|
|
30
|
+
'hidden',
|
|
31
|
+
'pressed',
|
|
32
|
+
'parallaxTarget',
|
|
33
|
+
'spread',
|
|
34
|
+
'expanded',
|
|
35
|
+
'settled',
|
|
36
|
+
'mode',
|
|
37
|
+
'active',
|
|
38
|
+
'islandToggle',
|
|
39
|
+
'islandPanel',
|
|
40
|
+
'islandItem',
|
|
41
|
+
'islandClose',
|
|
42
|
+
'reelTrack',
|
|
43
|
+
'reelCopy',
|
|
44
|
+
'dragging',
|
|
45
|
+
'dockItem',
|
|
46
|
+
]);
|
|
47
|
+
|
|
48
|
+
const coerce = (value: string): unknown => {
|
|
49
|
+
if (value === '' || value === 'true') return true;
|
|
50
|
+
if (value === 'false') return false;
|
|
51
|
+
if (/^-?\d*\.?\d+$/.test(value)) return Number(value);
|
|
52
|
+
return value;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* `data-mk-<option>` attributes → options object.
|
|
57
|
+
* `data-mk-throw-boost="2.5"` → `{ throwBoost: 2.5 }`, `data-mk-trigger="load"` → `{ trigger: 'load' }`.
|
|
58
|
+
*/
|
|
59
|
+
export function parseOptions(el: HTMLElement): Record<string, unknown> {
|
|
60
|
+
const options: Record<string, unknown> = {};
|
|
61
|
+
for (const [key, raw] of Object.entries(el.dataset)) {
|
|
62
|
+
if (!key.startsWith('mk') || key.length === 2) continue;
|
|
63
|
+
const name = key.charAt(2).toLowerCase() + key.slice(3);
|
|
64
|
+
if (RESERVED.has(name)) continue;
|
|
65
|
+
options[name] = coerce(raw ?? '');
|
|
66
|
+
}
|
|
67
|
+
return options;
|
|
68
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# chat
|
|
2
|
+
|
|
3
|
+
El chat de Stepbro (PrettyChat): burbujas de cristal sobre el fondo vivo, desvanecido de 64 px abajo, composer de 64 px que crece con el texto.
|
|
4
|
+
|
|
5
|
+
- **Mensaje nuevo:** la burbuja crece desde el lado de quien habla (`scale .5 → 1`, blur 24 px, 0.7 s curva firma, 60 ms entre burbujas) y sus palabras suben (16 px, blur 6, 0.25 s, 50 ms, `back.out`). La conversación baja al último (0.35 s).
|
|
6
|
+
- **Pensando:** `pending()` muestra "Pensando…" solo si la respuesta tarda más de 500 ms (con brillo que barre en 1.1 s). La respuesta se transforma desde ese hueco (Flip 0.7 s, sin escalar el texto) mientras sus palabras suben.
|
|
7
|
+
- **Composer:** crece/encoge con el texto (0.18 s, `power2.out`, desde 64 px). Enter envía, Shift+Enter salta de línea.
|
|
8
|
+
- **Confirmación:** una barra entra con flow y sus palabras suben (100 ms entre palabras).
|
|
9
|
+
- **Grabando:** el botón de micrófono pulsa (1.5 s).
|
|
10
|
+
|
|
11
|
+
Origen: Stepbro `pretty-chat`, `chat-message-enter`, `chat-pending-to-reply`, `chat-composer-resize`, `chat-confirm` (kit Money).
|
|
12
|
+
|
|
13
|
+
## HTML / Astro
|
|
14
|
+
|
|
15
|
+
```html
|
|
16
|
+
<div style="height: 520px">
|
|
17
|
+
<mk-chat background mic placeholder="Escribe un mensaje...">
|
|
18
|
+
<div data-mk-message data-mk-role="assistant"><span data-mk-text>Hola, ¿qué hacemos hoy?</span></div>
|
|
19
|
+
</mk-chat>
|
|
20
|
+
</div>
|
|
21
|
+
|
|
22
|
+
<script>
|
|
23
|
+
import { defineElements } from '@yewdiel21/motion-kit/elements';
|
|
24
|
+
defineElements(['mk-chat']);
|
|
25
|
+
const chat = document.querySelector('mk-chat');
|
|
26
|
+
chat.addEventListener('mk:send', async (event) => {
|
|
27
|
+
chat.add('user', event.detail.text);
|
|
28
|
+
chat.pending();
|
|
29
|
+
const answer = await miApi(event.detail.text);
|
|
30
|
+
chat.reply(answer);
|
|
31
|
+
});
|
|
32
|
+
</script>
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Vacío, `<mk-chat>` construye la lista y el composer; los `[data-mk-message]` hijos pasan a la lista como historial (quietos, salvo `animate-existing`). Con tu propio marcado: `[data-mk-part="messages"]`, `[data-mk-part="composer"]` (un `<form>`), `[data-mk-part="input"]`, `[data-mk-part="send"]`, `[data-mk-part="mic"]`; y `createChat(root, opciones)`.
|
|
36
|
+
|
|
37
|
+
## React
|
|
38
|
+
|
|
39
|
+
```tsx
|
|
40
|
+
const [messages, setMessages] = useState<{ id: string; role: 'user' | 'assistant'; text: string }[]>([]);
|
|
41
|
+
const [pending, setPending] = useState(false);
|
|
42
|
+
|
|
43
|
+
<div style={{ height: 520 }}>
|
|
44
|
+
<Chat.Root
|
|
45
|
+
onSend={async (text) => {
|
|
46
|
+
setMessages((m) => [...m, { id: crypto.randomUUID(), role: 'user', text }]);
|
|
47
|
+
setPending(true);
|
|
48
|
+
const answer = await miApi(text);
|
|
49
|
+
setMessages((m) => [...m, { id: crypto.randomUUID(), role: 'assistant', text: answer }]);
|
|
50
|
+
setPending(false); // en el mismo render que el mensaje: así se transforma desde "Pensando…"
|
|
51
|
+
}}
|
|
52
|
+
>
|
|
53
|
+
<ChatBackground />
|
|
54
|
+
<Chat.Messages pending={pending}>
|
|
55
|
+
{messages.map((m) => (
|
|
56
|
+
<Chat.Message key={m.id} role={m.role}>{m.text}</Chat.Message>
|
|
57
|
+
))}
|
|
58
|
+
</Chat.Messages>
|
|
59
|
+
<Chat.Composer />
|
|
60
|
+
</Chat.Root>
|
|
61
|
+
</div>
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
`useChat()` (dentro de `Chat.Root`) da el motor: `setRecording(on)`, `scrollToLatest()`. `<Chat.Confirm>` anima al montarse.
|
|
65
|
+
|
|
66
|
+
## API
|
|
67
|
+
|
|
68
|
+
`createChat(root, { animateExisting, clearOnSend, labels: { thinking }, onSend })` →
|
|
69
|
+
|
|
70
|
+
| Método | Qué hace |
|
|
71
|
+
|---|---|
|
|
72
|
+
| `add(role, texto)` | Añade un mensaje de texto (entra solo) |
|
|
73
|
+
| `pending()` | "Pensando…" a los 500 ms |
|
|
74
|
+
| `reply(el \| texto)` | La respuesta: se transforma desde "Pensando…" o entra normal |
|
|
75
|
+
| `confirm(barra)` | Barra de confirmación con flow + palabras |
|
|
76
|
+
| `setRecording(on)` | Pulso en `[data-mk-part="mic"]` |
|
|
77
|
+
| `scrollToLatest()` | Baja al último mensaje |
|
|
78
|
+
| `adopt(el)` | Marca un mensaje como ya animado (para frameworks) |
|
|
79
|
+
|
|
80
|
+
Cualquier `[data-mk-message]` que entre en la lista (tuyo, de un framework o de `add`) se anima solo. Evento `mk:send` (`detail.text`, cancelable) antes de `onSend`.
|
|
81
|
+
|
|
82
|
+
Para otros frameworks: `capturePending(placeholder)` antes de cambiar el DOM y `morphPendingToReply(estado, respuesta, lista)` después.
|
|
83
|
+
|
|
84
|
+
## Variables CSS
|
|
85
|
+
|
|
86
|
+
`--mk-chat-radius` (40px), `--mk-chat-bubble-radius` (32px), `--mk-chat-user-bg`, `--mk-chat-user-fg`, `--mk-chat-assistant-bg`, `--mk-chat-assistant-fg`.
|
|
87
|
+
|
|
88
|
+
## Accesibilidad
|
|
89
|
+
|
|
90
|
+
La lista es `role="log"` con `aria-live="polite"`. El textarea y los botones tienen nombre (placeholder, "Enviar", "Micrófono"). Con movimiento reducido los mensajes aparecen sin animar y el brillo y el pulso se paran.
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
/* chat — Stepbro's PrettyChat: glass bubbles over the live background, a soft fade at the bottom, a 64 px composer. */
|
|
2
|
+
.mk-chat,
|
|
3
|
+
mk-chat {
|
|
4
|
+
--mk-chat-radius: var(--mk-radius-2xl);
|
|
5
|
+
--mk-chat-bubble-radius: var(--mk-radius-bubble);
|
|
6
|
+
--mk-chat-user-bg: color-mix(in srgb, var(--md-sys-color-on-background) 62%, transparent);
|
|
7
|
+
--mk-chat-user-fg: var(--md-sys-color-background);
|
|
8
|
+
--mk-chat-assistant-bg: color-mix(in srgb, var(--md-sys-color-background) 58%, transparent);
|
|
9
|
+
--mk-chat-assistant-fg: var(--md-sys-color-on-surface);
|
|
10
|
+
position: relative;
|
|
11
|
+
display: flex;
|
|
12
|
+
flex-direction: column;
|
|
13
|
+
height: 100%;
|
|
14
|
+
min-height: 0;
|
|
15
|
+
overflow: hidden;
|
|
16
|
+
border-radius: var(--mk-chat-radius);
|
|
17
|
+
color: var(--mk-text);
|
|
18
|
+
font-family: var(--mk-font);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
.mk-chat > .mk-chat-background,
|
|
22
|
+
mk-chat > .mk-chat-background,
|
|
23
|
+
mk-chat > mk-chat-background {
|
|
24
|
+
z-index: 0;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
:is(.mk-chat, mk-chat) [data-mk-part='messages'] {
|
|
28
|
+
--mk-chat-fade: 64px;
|
|
29
|
+
position: relative;
|
|
30
|
+
z-index: 1;
|
|
31
|
+
display: flex;
|
|
32
|
+
flex: 1 1 0;
|
|
33
|
+
flex-direction: column;
|
|
34
|
+
gap: 12px;
|
|
35
|
+
min-height: 0;
|
|
36
|
+
padding: 16px 16px 80px;
|
|
37
|
+
overflow-y: auto;
|
|
38
|
+
-webkit-mask-image: linear-gradient(to bottom, #000 calc(100% - var(--mk-chat-fade)), transparent 100%);
|
|
39
|
+
mask-image: linear-gradient(to bottom, #000 calc(100% - var(--mk-chat-fade)), transparent 100%);
|
|
40
|
+
overscroll-behavior: contain;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
:is(.mk-chat, mk-chat) [data-mk-message] {
|
|
44
|
+
position: relative;
|
|
45
|
+
z-index: 10;
|
|
46
|
+
display: block;
|
|
47
|
+
flex: 0 0 auto;
|
|
48
|
+
width: fit-content;
|
|
49
|
+
max-width: 80%;
|
|
50
|
+
padding: 16px 24px;
|
|
51
|
+
overflow: hidden;
|
|
52
|
+
border-radius: var(--mk-chat-bubble-radius);
|
|
53
|
+
font-size: 16px;
|
|
54
|
+
font-weight: 500;
|
|
55
|
+
line-height: 1.3;
|
|
56
|
+
white-space: pre-wrap;
|
|
57
|
+
overflow-wrap: anywhere;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
:is(.mk-chat, mk-chat) [data-mk-message][data-mk-role='user'] {
|
|
61
|
+
align-self: flex-end;
|
|
62
|
+
background: var(--mk-chat-user-bg);
|
|
63
|
+
color: var(--mk-chat-user-fg);
|
|
64
|
+
-webkit-backdrop-filter: blur(16px);
|
|
65
|
+
backdrop-filter: blur(16px);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
:is(.mk-chat, mk-chat) [data-mk-message][data-mk-role='assistant'] {
|
|
69
|
+
align-self: flex-start;
|
|
70
|
+
background: var(--mk-chat-assistant-bg);
|
|
71
|
+
color: var(--mk-chat-assistant-fg);
|
|
72
|
+
-webkit-backdrop-filter: blur(16px);
|
|
73
|
+
backdrop-filter: blur(16px);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
:is(.mk-chat, mk-chat) [data-mk-message][data-mk-role='system'] {
|
|
77
|
+
align-self: center;
|
|
78
|
+
max-width: 90%;
|
|
79
|
+
padding: 8px 16px;
|
|
80
|
+
background: none;
|
|
81
|
+
color: color-mix(in srgb, var(--md-sys-color-on-background) 62%, transparent);
|
|
82
|
+
font-size: 14px;
|
|
83
|
+
text-align: center;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
@media (max-width: 679px) {
|
|
87
|
+
:is(.mk-chat, mk-chat) [data-mk-message][data-mk-role='assistant'] {
|
|
88
|
+
max-width: 100%;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
:is(.mk-chat, mk-chat) [data-mk-text] {
|
|
93
|
+
display: block;
|
|
94
|
+
white-space: pre-wrap;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
:is(.mk-chat, mk-chat) .mk-split-word {
|
|
98
|
+
white-space: pre;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/* The thinking placeholder and its glow sweep (1.1 s). */
|
|
102
|
+
:is(.mk-chat, mk-chat) [data-mk-pending] {
|
|
103
|
+
position: relative;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
:is(.mk-chat, mk-chat) [data-mk-pending]::after {
|
|
107
|
+
position: absolute;
|
|
108
|
+
inset: 0 auto 0 0;
|
|
109
|
+
z-index: 2;
|
|
110
|
+
width: 50%;
|
|
111
|
+
background: radial-gradient(ellipse at center, var(--md-sys-color-background), transparent 70%);
|
|
112
|
+
content: '';
|
|
113
|
+
animation: mk-chat-glow 1.1s ease-in-out infinite;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
.mk-chat__thinking {
|
|
117
|
+
display: inline-flex;
|
|
118
|
+
align-items: center;
|
|
119
|
+
gap: 8px;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
@keyframes mk-chat-glow {
|
|
123
|
+
from {
|
|
124
|
+
transform: translate(-100%);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
to {
|
|
128
|
+
transform: translate(200%);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/* Composer: the textarea grows from 64 px; round 64 px buttons. */
|
|
133
|
+
:is(.mk-chat, mk-chat) [data-mk-part='composer'] {
|
|
134
|
+
position: relative;
|
|
135
|
+
z-index: 2;
|
|
136
|
+
display: flex;
|
|
137
|
+
align-items: flex-end;
|
|
138
|
+
gap: 8px;
|
|
139
|
+
margin: 0;
|
|
140
|
+
padding: 8px;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
:is(.mk-chat, mk-chat) [data-mk-part='input'],
|
|
144
|
+
:is(.mk-chat, mk-chat) [data-mk-part='composer'] textarea {
|
|
145
|
+
flex: 1;
|
|
146
|
+
min-width: 0;
|
|
147
|
+
min-height: 64px;
|
|
148
|
+
max-height: 40vh;
|
|
149
|
+
margin: 0;
|
|
150
|
+
padding: 18px 16px;
|
|
151
|
+
overflow: hidden;
|
|
152
|
+
box-sizing: border-box;
|
|
153
|
+
border: 0;
|
|
154
|
+
background: transparent;
|
|
155
|
+
color: inherit;
|
|
156
|
+
font: inherit;
|
|
157
|
+
font-size: 24px;
|
|
158
|
+
line-height: 1.3;
|
|
159
|
+
resize: none;
|
|
160
|
+
outline: none;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
:is(.mk-chat, mk-chat) [data-mk-part='input']::placeholder {
|
|
164
|
+
color: var(--md-sys-color-on-background);
|
|
165
|
+
opacity: 0.5;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
:is(.mk-chat, mk-chat) [data-mk-part='buttons'] {
|
|
169
|
+
display: flex;
|
|
170
|
+
gap: 8px;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
:is(.mk-chat, mk-chat) :is([data-mk-part='mic'], [data-mk-part='send']) {
|
|
174
|
+
display: grid;
|
|
175
|
+
flex: none;
|
|
176
|
+
place-items: center;
|
|
177
|
+
width: 64px;
|
|
178
|
+
height: 64px;
|
|
179
|
+
padding: 0;
|
|
180
|
+
border: 0;
|
|
181
|
+
border-radius: 32px;
|
|
182
|
+
background: color-mix(in srgb, var(--md-sys-color-background) 40%, transparent);
|
|
183
|
+
color: inherit;
|
|
184
|
+
cursor: pointer;
|
|
185
|
+
-webkit-backdrop-filter: blur(16px);
|
|
186
|
+
backdrop-filter: blur(16px);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
:is(.mk-chat, mk-chat) :is([data-mk-part='mic'], [data-mk-part='send']):disabled {
|
|
190
|
+
opacity: 0.6;
|
|
191
|
+
cursor: not-allowed;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
:is(.mk-chat, mk-chat) :is([data-mk-part='mic'], [data-mk-part='send']):focus-visible {
|
|
195
|
+
outline: 2px solid var(--mk-accent);
|
|
196
|
+
outline-offset: 2px;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
:is(.mk-chat, mk-chat) [data-mk-recording] {
|
|
200
|
+
background: color-mix(in srgb, var(--md-sys-color-error) 50%, transparent);
|
|
201
|
+
animation: mk-chat-recording 1.5s ease-in-out infinite;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
@keyframes mk-chat-recording {
|
|
205
|
+
0%,
|
|
206
|
+
100% {
|
|
207
|
+
opacity: 1;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
50% {
|
|
211
|
+
opacity: 0.6;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/* A confirmation bar above the composer. */
|
|
216
|
+
:is(.mk-chat, mk-chat) [data-mk-part='confirm'] {
|
|
217
|
+
position: relative;
|
|
218
|
+
z-index: 2;
|
|
219
|
+
display: flex;
|
|
220
|
+
flex-wrap: wrap;
|
|
221
|
+
align-items: center;
|
|
222
|
+
gap: 8px;
|
|
223
|
+
margin: 0 16px 8px;
|
|
224
|
+
padding: 12px 16px;
|
|
225
|
+
border-radius: var(--mk-radius-lg);
|
|
226
|
+
background: var(--mk-chat-assistant-bg);
|
|
227
|
+
-webkit-backdrop-filter: blur(16px);
|
|
228
|
+
backdrop-filter: blur(16px);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
@media (prefers-reduced-motion: reduce) {
|
|
232
|
+
:is(.mk-chat, mk-chat) [data-mk-pending]::after,
|
|
233
|
+
:is(.mk-chat, mk-chat) [data-mk-recording] {
|
|
234
|
+
animation: none;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { MkElement, defineElement, type OptionType } from '../../elements/base';
|
|
2
|
+
import type { Instance } from '../../core/types';
|
|
3
|
+
import { iconSvg } from '../icons';
|
|
4
|
+
import { defineChatBackground } from '../chat-background/element';
|
|
5
|
+
import { createChat, type ChatInstance, type ChatRole } from './engine';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* <mk-chat placeholder="Escribe un mensaje..." [background] [mic] [animate-existing]>
|
|
9
|
+
* (optional) your own [data-mk-part="messages"] / composer markup
|
|
10
|
+
* </mk-chat>
|
|
11
|
+
* Empty, it builds the messages list and the composer. Listen to `mk:send`
|
|
12
|
+
* (detail.text) and answer with `element.reply(text)` after `element.pending()`.
|
|
13
|
+
*/
|
|
14
|
+
export class MkChatElement extends MkElement<Instance> {
|
|
15
|
+
static options: Record<string, OptionType> = {
|
|
16
|
+
placeholder: 'string',
|
|
17
|
+
background: 'boolean',
|
|
18
|
+
mic: 'boolean',
|
|
19
|
+
animateExisting: 'boolean',
|
|
20
|
+
thinking: 'string',
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
chat: ChatInstance | null = null;
|
|
24
|
+
|
|
25
|
+
protected create(options: Record<string, unknown>): Instance {
|
|
26
|
+
const made: Element[] = [];
|
|
27
|
+
if (!this.querySelector('[data-mk-part="messages"]')) {
|
|
28
|
+
if (options.background && !this.querySelector('.mk-chat-background, mk-chat-background')) {
|
|
29
|
+
defineChatBackground();
|
|
30
|
+
const background = document.createElement('mk-chat-background');
|
|
31
|
+
this.prepend(background);
|
|
32
|
+
made.push(background);
|
|
33
|
+
}
|
|
34
|
+
const messages = document.createElement('div');
|
|
35
|
+
messages.dataset.mkPart = 'messages';
|
|
36
|
+
// Messages written inside the element (history) move into the list.
|
|
37
|
+
messages.append(...[...this.children].filter((child) => child.matches('[data-mk-message]')));
|
|
38
|
+
const composer = document.createElement('form');
|
|
39
|
+
composer.dataset.mkPart = 'composer';
|
|
40
|
+
const input = document.createElement('textarea');
|
|
41
|
+
input.dataset.mkPart = 'input';
|
|
42
|
+
input.rows = 1;
|
|
43
|
+
input.placeholder = (options.placeholder as string | undefined) ?? 'Escribe un mensaje...';
|
|
44
|
+
input.setAttribute('aria-label', input.placeholder);
|
|
45
|
+
const buttons = document.createElement('div');
|
|
46
|
+
buttons.dataset.mkPart = 'buttons';
|
|
47
|
+
if (options.mic) {
|
|
48
|
+
const mic = document.createElement('button');
|
|
49
|
+
mic.type = 'button';
|
|
50
|
+
mic.dataset.mkPart = 'mic';
|
|
51
|
+
mic.setAttribute('aria-label', 'Micrófono');
|
|
52
|
+
mic.innerHTML = iconSvg('mic', 22);
|
|
53
|
+
buttons.append(mic);
|
|
54
|
+
}
|
|
55
|
+
const send = document.createElement('button');
|
|
56
|
+
send.type = 'submit';
|
|
57
|
+
send.dataset.mkPart = 'send';
|
|
58
|
+
send.setAttribute('aria-label', 'Enviar');
|
|
59
|
+
send.innerHTML = iconSvg('send', 22);
|
|
60
|
+
buttons.append(send);
|
|
61
|
+
composer.append(input, buttons);
|
|
62
|
+
this.append(messages, composer);
|
|
63
|
+
made.push(messages, composer);
|
|
64
|
+
}
|
|
65
|
+
const chat = createChat(this, {
|
|
66
|
+
animateExisting: Boolean(options.animateExisting),
|
|
67
|
+
labels: { thinking: options.thinking as string | undefined },
|
|
68
|
+
});
|
|
69
|
+
this.chat = chat;
|
|
70
|
+
return {
|
|
71
|
+
destroy: () => {
|
|
72
|
+
chat.destroy();
|
|
73
|
+
const messages = made.find((node) => node instanceof HTMLElement && node.dataset.mkPart === 'messages');
|
|
74
|
+
if (messages) this.append(...[...messages.children]);
|
|
75
|
+
for (const node of made) node.remove();
|
|
76
|
+
this.chat = null;
|
|
77
|
+
},
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
add(role: ChatRole, text: string) {
|
|
82
|
+
return this.chat?.add(role, text);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
pending() {
|
|
86
|
+
this.chat?.pending();
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
reply(message: HTMLElement | string) {
|
|
90
|
+
return this.chat?.reply(message);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
setRecording(on: boolean) {
|
|
94
|
+
this.chat?.setRecording(on);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export const defineChat = () => defineElement('mk-chat', MkChatElement);
|