morphico 0.2.0 → 0.4.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/README.md CHANGED
@@ -19,7 +19,7 @@ Or from a CDN, as a classic script with the global `morphico`:
19
19
  ```html
20
20
  <script src="https://cdn.jsdelivr.net/npm/morphico"></script>
21
21
  <script>
22
- const { icon, text } = morphico;
22
+ const { icon, text, clip } = morphico;
23
23
  </script>
24
24
  ```
25
25
 
@@ -79,6 +79,80 @@ clock.destroy();
79
79
  (`5` ↔ `:`). Pad counters with spaces to keep them morphing: `counter.set(String(count).padStart(3, ' '))`.
80
80
  - Digits sit on the text baseline, and every cell has the same width, so clocks don't jiggle.
81
81
 
82
+ ## A cut-out: `clip(element, options)`
83
+
84
+ Cuts any element (an `<img>`, a `<div>`, a `<main>` …) out by a shape, and morphs, moves, resizes and turns it.
85
+
86
+ ```ts
87
+ const photo = clip(document.querySelector('#photo'), { state: 'star', x: 50, y: 50, size: '80%' });
88
+
89
+ await photo.to('heart'); // morph
90
+ photo.to('shield', { rotation: 360, x: 30, size: '120px' }); // morph + turn + move together
91
+ photo.to({ x: 70, y: '150px' }); // move only, the shape stays
92
+ photo.set({ rotation: 15 }); // jump, no animation (e.g. while dragging a slider)
93
+ photo.set({ outline: { width: 16 } }); // only a 16 px band along the edge shows
94
+ photo.set({ inverted: true }); // the other way round: the shape is a hole
95
+ photo.destroy(); // the element is whole again
96
+ ```
97
+
98
+ Shapes (based on [Lucide](https://lucide.dev)): `circle`, `ellipse`, `heart`, `sparkle`, `astroid`, `bookmark`,
99
+ `shield`, `moon`, `tag`, `square`, `badge`, `triangle`, `droplet`, `pentagon`, `hexagon`, `octagon`, `squircle`,
100
+ `star`, `rectangle-horizontal`, `message-circle`, `message-square`, `cloud`, `puzzle`, `club`. A diamond is the `square`
101
+ turned 45°, a portrait rectangle `rectangle-horizontal` turned 90° (`rotation`).
102
+
103
+ | Option | Default | |
104
+ | --- | --- | --- |
105
+ | `state` | `'circle'` | The shape |
106
+ | `x`, `y` | `50` | The shape's **centre**. A number is %, or a string `'40%'` / `'120px'` (% of the element's width for `x`, height for `y`) |
107
+ | `size` | fits | The side of the shape's square (% of the element's width, or px). The shape keeps its own proportions inside that square. Default: the largest square that fits the element |
108
+ | `rotation` | `0` | Degrees, clockwise, **absolute**: `rotation: 360` ends one full turn from 0. The shape turns rigidly, also in a wide or tall element |
109
+ | `roundness` | `1` | Corners: `0` = sharp … `1` = the shape's own (the maximum) |
110
+ | `outline` | `false` | `{ width, band }`: only a band of `width` px along the edge shows; `band`: `'inside'` (default), `'centre'` or `'outside'` the edge. Not animated |
111
+ | `inverted` | `false` | `true`: the other way round — the shape (or band) is a hole, the rest of the element shows. Not animated |
112
+ | `duration`, `easing` | `400`, `'easeInOutCubic'` | As for icons; also per `to()` call |
113
+
114
+ Everything except `outline` and `inverted` animates together in `to()`. How it works: a filled shape uses CSS `clip-path: url(#…)`,
115
+ an outlined one `mask: url(#…)` (clip-path has no line width); the `<clipPath>` / `<mask>` sit in a small hidden
116
+ `<svg>` that `clip()` adds to the page. It follows the element's size (`ResizeObserver`). Overflow beyond the element is
117
+ cut off, as with any clip-path.
118
+
119
+ ## A face: `face(element, options)`
120
+
121
+ A face of 7 soft pieces — 2 brows, 2 eyes, 2 pupils and a mouth — that morphs between feelings and blinks.
122
+
123
+ ```html
124
+ <div id="robot" style="width: 160px; height: 160px; border-radius: 50%; background: #e4e2d6; color: #8b8e8e"></div>
125
+ ```
126
+
127
+ ```ts
128
+ import { face } from 'morphico';
129
+
130
+ const robot = face(document.querySelector('#robot'), { state: 'normal' });
131
+
132
+ await robot.to('smiling'); // morph
133
+ robot.to('angry', { duration: 300, onEnd: (result) => console.log(result) });
134
+ robot.blink(); // close and open the eyes once, on top of any expression
135
+ robot.state; // 'angry'
136
+ robot.destroy(); // removes the face, stops blinking
137
+ ```
138
+
139
+ Expressions: `normal`, `smiling`, `pleading`, `wonder`, `focused`, `fright`, `angry`, `asleep`, `sad`,
140
+ `surprised`, `laughing`, `tired`, `wink-left`, `wink-right`, `suspicious`, `confused`, `smug`, `irritated`,
141
+ `bored`, `curious`, `empathetic`, `guilty`, `worried`, `disappointed`, `ashamed`, `embarrassed`, `yearning`,
142
+ `triumphant`, `proud`, `excited`, `scornful`, `disgusted` (also exported as `faceExpressionNames`). Any one
143
+ morphs into any other. `wink-left` / `wink-right` = the eye that closes, as seen.
144
+
145
+ | Option | Default | |
146
+ | --- | --- | --- |
147
+ | `state` | `'normal'` | The expression shown first |
148
+ | `pupilColor` | `'white'` | Colour of the pupils. Brows, eyes and mouth take the CSS `color` |
149
+ | `blink` | `true` | Blink by itself every few seconds |
150
+ | `size` | `'100%'` | CSS width and height of the face (by default it fills the element) |
151
+ | `duration`, `easing` | `500`, `'easeInOutCubic'` | As for icons; also per `to()` call |
152
+
153
+ The face draws no background: a disc, card or anything else behind it is the element's own CSS. The pupils only show
154
+ inside the eyes (an eyelid covers them). With reduced motion the face changes without animation and doesn't blink.
155
+
82
156
  ## Options
83
157
 
84
158
  | Option | For | Default | |
@@ -106,7 +180,7 @@ clock.destroy();
106
180
  The earlier promise resolves with `{ interrupted: true }`; promises never stay pending.
107
181
  - **Reduced motion:** when the user's system asks for reduced motion, shapes change without animation.
108
182
  - **Accessibility:** the icon / line has `role="img"` and an `aria-label` with the current value.
109
- - **Server-side rendering:** importing does not touch the DOM; only `icon()` / `text()` do.
183
+ - **Server-side rendering:** importing does not touch the DOM; only `icon()` / `text()` / `clip()` / `face()` do.
110
184
 
111
185
  ## Development
112
186
 
@@ -121,5 +195,5 @@ npm run build # src/ → dist/
121
195
 
122
196
  ## License
123
197
 
124
- [MIT](LICENSE). The icon sets are based on [Lucide](https://lucide.dev) icons (ISC; some from Feather, MIT):
198
+ [MIT](LICENSE). The icon sets and clip shapes are based on [Lucide](https://lucide.dev) icons (ISC; some from Feather, MIT):
125
199
  see [LICENSE-lucide](LICENSE-lucide).