flet-box 1.1.0 → 1.1.2

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 (135) hide show
  1. package/README.md +16 -12
  2. package/bin/commands/checkUi.js +276 -45
  3. package/bin/commands/create.js +8 -1
  4. package/bin/commands/createBundle.js +18 -6
  5. package/bin/commands/createScreen.js +1 -1
  6. package/bin/commands/runBundle.js +1 -1
  7. package/bin/utils/bundleConfig.js +47 -3
  8. package/bin/utils/colors.js +0 -8
  9. package/bin/utils/helpers.js +0 -5
  10. package/bin/utils/images/jpeg.js +2 -3
  11. package/bin/utils/templates.js +101 -85
  12. package/docs/README.md +24 -1
  13. package/docs/cli/README.md +51 -10
  14. package/docs/guides/README.md +5 -2
  15. package/docs/guides/accessibility.md +282 -0
  16. package/docs/guides/clean-ui.md +53 -8
  17. package/docs/guides/first-app.md +6 -7
  18. package/docs/guides/minification-and-protection.md +1 -1
  19. package/docs/guides/mobile-and-platforms.md +51 -0
  20. package/docs/guides/online-updates.md +18 -6
  21. package/docs/guides/utilities.md +116 -4
  22. package/docs/guides/widget-structure.md +30 -0
  23. package/docs/tools/README.md +1 -1
  24. package/docs/tools/animation.md +134 -11
  25. package/docs/tools/device.md +75 -3
  26. package/docs/tools/text-and-time.md +116 -6
  27. package/docs/widget/Accordion.md +2 -0
  28. package/docs/widget/AnimatedBox.md +46 -20
  29. package/docs/widget/AnimatedText.md +13 -7
  30. package/docs/widget/Badge.md +1 -0
  31. package/docs/widget/COMMON_PROPS.md +26 -3
  32. package/docs/widget/Chart.md +1 -0
  33. package/docs/widget/Checkbox.md +7 -1
  34. package/docs/widget/CircularChart.md +2 -0
  35. package/docs/widget/Drawer.md +1 -0
  36. package/docs/widget/GridView.md +4 -1
  37. package/docs/widget/Input.md +2 -0
  38. package/docs/widget/InstallButton.md +1 -0
  39. package/docs/widget/Markdown.md +4 -0
  40. package/docs/widget/Rating.md +2 -0
  41. package/docs/widget/Slider.md +2 -0
  42. package/docs/widget/Stack.md +6 -5
  43. package/docs/widget/Stepper.md +2 -0
  44. package/docs/widget/Tooltip.md +3 -1
  45. package/package.json +3 -2
  46. package/skills/fletbox-design/SKILL.md +390 -14
  47. package/skills/fletbox-motion/SKILL.md +145 -47
  48. package/skills/fletbox-rules/SKILL.md +140 -1
  49. package/src/animations/AnimatedBox.js +321 -226
  50. package/src/animations/MatrixRain.js +7 -1
  51. package/src/animations/ParallaxBox.js +13 -10
  52. package/src/animations/animate.js +269 -106
  53. package/src/animations/color.js +91 -0
  54. package/src/animations/easings.js +275 -0
  55. package/src/animations/index.js +21 -0
  56. package/src/animations/motion.js +204 -0
  57. package/src/animations/transitions.js +327 -0
  58. package/src/core/runApp.js +0 -6
  59. package/src/helpers/boxEdges.js +69 -0
  60. package/src/helpers/html.js +39 -0
  61. package/src/index.d.ts +377 -25
  62. package/src/index.js +77 -0
  63. package/src/navigations/AdaptiveScaffold.js +6 -2
  64. package/src/navigations/AppBar.js +16 -7
  65. package/src/navigations/BottomNavigation.js +24 -1
  66. package/src/navigations/CollapsibleSideBar.js +0 -1
  67. package/src/navigations/Drawer.js +32 -1
  68. package/src/navigations/DrawerItem.js +1 -1
  69. package/src/navigations/Router.js +0 -1
  70. package/src/navigations/Scaffold.js +9 -3
  71. package/src/navigations/Tabs.js +104 -31
  72. package/src/services/RamStore.js +1 -2
  73. package/src/tools/breakpoints.js +113 -0
  74. package/src/tools/createList.js +0 -2
  75. package/src/tools/fonts.js +99 -0
  76. package/src/tools/index.js +74 -1
  77. package/src/tools/margin.js +5 -63
  78. package/src/tools/padding.js +5 -63
  79. package/src/tools/print.js +0 -8
  80. package/src/tools/scale.js +9 -2
  81. package/src/tools/time.js +56 -15
  82. package/src/tools/useState.js +1 -54
  83. package/src/utils/fieldFont.js +77 -0
  84. package/src/utils/focusRing.js +73 -0
  85. package/src/utils/focusScope.js +113 -0
  86. package/src/utils/markdownParser.js +7 -9
  87. package/src/utils/readableInk.js +7 -1
  88. package/src/utils/stackLayout.js +55 -0
  89. package/src/utils/styleInjector.js +25 -0
  90. package/src/utils/syntaxHighlight.js +1 -17
  91. package/src/utils/themes.js +15 -0
  92. package/src/utils/visualEffects.js +49 -47
  93. package/src/widget-factory/createWidget.js +16 -3
  94. package/src/widget-factory/effects.js +76 -3
  95. package/src/widget-factory/reactiveValue.js +0 -7
  96. package/src/widget-factory/tools.js +26 -143
  97. package/src/widget-factory/translateProps.js +5 -0
  98. package/src/widget-factory/widgetFactory.js +1 -1
  99. package/src/widgets/Accordion.js +70 -12
  100. package/src/widgets/AdMob.js +1 -1
  101. package/src/widgets/AdSense.js +1 -1
  102. package/src/widgets/AlertDialog.js +11 -9
  103. package/src/widgets/Avatar.js +10 -1
  104. package/src/widgets/Badge.js +7 -1
  105. package/src/widgets/BottomSheet.js +39 -5
  106. package/src/widgets/Chart.js +41 -9
  107. package/src/widgets/Checkbox.js +59 -9
  108. package/src/widgets/Chip.js +13 -1
  109. package/src/widgets/CircularBar.js +17 -5
  110. package/src/widgets/CircularChart.js +24 -2
  111. package/src/widgets/CodeViewer.js +5 -3
  112. package/src/widgets/Dropdown.js +223 -20
  113. package/src/widgets/GridView.js +17 -1
  114. package/src/widgets/Icon.js +21 -0
  115. package/src/widgets/Input.js +75 -44
  116. package/src/widgets/InstallButton.js +8 -2
  117. package/src/widgets/ListTile.js +6 -5
  118. package/src/widgets/ListView.js +21 -7
  119. package/src/widgets/Markdown.js +25 -44
  120. package/src/widgets/Modal.js +43 -6
  121. package/src/widgets/Pagination.js +35 -3
  122. package/src/widgets/ProgressBar.js +18 -0
  123. package/src/widgets/Radio.js +7 -0
  124. package/src/widgets/Rating.js +104 -16
  125. package/src/widgets/Skeleton.js +9 -5
  126. package/src/widgets/Slider.js +87 -7
  127. package/src/widgets/SnackBar.js +30 -1
  128. package/src/widgets/Stack.js +25 -13
  129. package/src/widgets/Stepper.js +112 -64
  130. package/src/widgets/Switch.js +10 -1
  131. package/src/widgets/Tooltip.js +93 -10
  132. package/src/widgets/TreeView.js +5 -5
  133. package/src/create-index.sh +0 -237
  134. package/src/widget-factory/pixTool.js +0 -40
  135. package/src/widget-factory/stringTool.js +0 -26
@@ -0,0 +1,327 @@
1
+ /**
2
+ * transitions.js - The named entrances and feedback gestures.
3
+ *
4
+ * Between an app that animates and one that looks animated there are three
5
+ * movements: something arriving, something leaving, and something saying "that
6
+ * was wrong". Writing them per screen is how a codebase ends up with eleven
7
+ * slightly different entrances, and each one is a hand-rolled loop that no longer
8
+ * obeys the reduced-motion preference because the engine never saw it.
9
+ *
10
+ * These are the three that carry a product, built on the same `runAnimation()`
11
+ * loop everything else uses, so they inherit its guarantees for free: one
12
+ * registered loop per widget and property, the end value always lands, a cancel
13
+ * function that also resolves an `await`, and duration collapsing to zero when the
14
+ * user asked for reduced motion.
15
+ *
16
+ * Every preset is described once, as the list of loops the gesture is made of, and
17
+ * the two forms — the one that hands back a cancel function and the one you `await`
18
+ * — are that same list run two ways. A preset that grows an option cannot end up
19
+ * meaning one thing to `slideIn` and another to `slideInAsync`.
20
+ *
21
+ * @module animations/transitions
22
+ */
23
+
24
+ import { runAnimation } from "./animate.js";
25
+
26
+ /**
27
+ * One loop of a gesture, as the arguments `runAnimation` takes.
28
+ *
29
+ * @typedef {object} GestureStep
30
+ * @property {string} property - The CSS property or transform function.
31
+ * @property {number|string} from - Starting value.
32
+ * @property {number|string} to - Ending value.
33
+ * @property {number} duration - Milliseconds.
34
+ * @property {string|((t: number) => number)} easing - Curve name or curve.
35
+ * @property {((progress: number) => number|string)} [valueAt] - Custom value curve.
36
+ */
37
+
38
+ /**
39
+ * Stops every loop a preset started on a widget.
40
+ *
41
+ * @param {(...: any) => boolean} cancels - The cancel functions of the loops.
42
+ * @returns {() => boolean} One cancel for the whole gesture.
43
+ */
44
+ const combine = (cancels) => () =>
45
+ cancels.map((cancel) => cancel()).some(Boolean);
46
+
47
+ /**
48
+ * Runs a gesture and hands back one cancel for the whole of it.
49
+ *
50
+ * @param {HTMLElement|object} widget - The target widget or DOM element.
51
+ * @param {GestureStep[]} steps
52
+ * @returns {() => boolean}
53
+ */
54
+ const play = (widget, steps) =>
55
+ combine(
56
+ steps.map(({ property, from, to, duration, easing, valueAt }) =>
57
+ runAnimation(widget, property, from, to, duration, easing, valueAt ?? null),
58
+ ),
59
+ );
60
+
61
+ /**
62
+ * Runs a gesture and resolves when every loop of it has landed.
63
+ *
64
+ * @param {HTMLElement|object} widget - The target widget or DOM element.
65
+ * @param {GestureStep[]} steps
66
+ * @returns {Promise<HTMLElement|object>} Resolves with `widget` — or as soon as any
67
+ * loop is cancelled or replaced, so `await` never hangs.
68
+ */
69
+ const playAsync = (widget, steps) =>
70
+ Promise.all(
71
+ steps.map(
72
+ ({ property, from, to, duration, easing, valueAt }) =>
73
+ new Promise((resolve) =>
74
+ runAnimation(
75
+ widget,
76
+ property,
77
+ from,
78
+ to,
79
+ duration,
80
+ easing,
81
+ valueAt ?? null,
82
+ resolve,
83
+ ),
84
+ ),
85
+ ),
86
+ ).then(() => widget);
87
+
88
+ /**
89
+ * Resolves the axis a `direction` names.
90
+ *
91
+ * The direction is the way the element *travels* as it arrives, which is how
92
+ * people say it out loud ("slide up"), and the axis it moves along is the second
93
+ * step: `up` and `down` are vertical travel, so the offset lives in `translateY`.
94
+ *
95
+ * @param {string} direction
96
+ * @returns {{ property: string, sign: number }}
97
+ */
98
+ const axisFor = (direction) => {
99
+ switch (direction) {
100
+ case "down":
101
+ return { property: "translateY", sign: -1 };
102
+ case "left":
103
+ return { property: "translateX", sign: 1 };
104
+ case "right":
105
+ return { property: "translateX", sign: -1 };
106
+ case "up":
107
+ default:
108
+ return { property: "translateY", sign: 1 };
109
+ }
110
+ };
111
+
112
+ /**
113
+ * The travel of a `slide`, as one framework-unit offset on the axis the direction
114
+ * names, plus the fade that carries it.
115
+ *
116
+ * @param {object} options - See {@link slideIn}.
117
+ * @param {"up"|"down"|"left"|"right"} [options.direction="up"]
118
+ * @param {number} [options.distance=16]
119
+ * @param {number} [options.duration=240]
120
+ * @param {string|((t: number) => number)} [options.easing="easeOut"]
121
+ * @param {boolean} [options.fade=true]
122
+ * @param {boolean} [options.leaving=false] - The exit mirrors the entrance.
123
+ * @returns {GestureStep[]}
124
+ */
125
+ const slideSteps = ({
126
+ direction = "up",
127
+ distance = 16,
128
+ duration = 240,
129
+ easing = "easeOut",
130
+ fade = true,
131
+ leaving = false,
132
+ }) => {
133
+ const { property, sign } = axisFor(direction);
134
+ const offset = Math.abs(Number(distance) || 0) * sign;
135
+ const steps = leaving
136
+ ? [{ property, from: 0, to: -offset, duration, easing }]
137
+ : [{ property, from: offset, to: 0, duration, easing }];
138
+ if (fade) {
139
+ steps.push({
140
+ property: "opacity",
141
+ from: leaving ? 1 : 0,
142
+ to: leaving ? 0 : 1,
143
+ duration,
144
+ easing,
145
+ });
146
+ }
147
+ return steps;
148
+ };
149
+
150
+ /**
151
+ * The decaying side-to-side (or up-and-down) movement of a `shake`.
152
+ *
153
+ * @param {object} options - See {@link shake}.
154
+ * @param {"x"|"y"} [options.axis="x"]
155
+ * @param {number} [options.distance=6]
156
+ * @param {number} [options.cycles=3]
157
+ * @param {number} [options.duration=320]
158
+ * @returns {GestureStep[]}
159
+ */
160
+ const shakeSteps = ({
161
+ axis = "x",
162
+ distance = 6,
163
+ cycles = 3,
164
+ duration = 320,
165
+ }) => {
166
+ const property = axis === "y" ? "translateY" : "translateX";
167
+ const amplitude = Math.abs(Number(distance) || 0);
168
+ const swings = Math.max(1, Number(cycles) || 3);
169
+ return [
170
+ {
171
+ property,
172
+ from: 0,
173
+ to: 0,
174
+ duration,
175
+ easing: "linear",
176
+ // (1 - t) makes each swing smaller than the last, so the gesture ends at rest
177
+ // instead of stopping mid-swing and leaving the row off its own column.
178
+ valueAt: (progress) =>
179
+ amplitude * Math.sin(progress * Math.PI * 2 * swings) * (1 - progress),
180
+ },
181
+ ];
182
+ };
183
+
184
+ /**
185
+ * Brings a widget onto the screen: a short travel plus a fade, as one gesture.
186
+ *
187
+ * This is the entrance the design rules describe (opacity + a small `translate`,
188
+ * one direction, under 500ms) with the arithmetic already done, so a screen does
189
+ * not need three `animate()` calls and a `Promise.all` to say "this appeared".
190
+ *
191
+ * @param {HTMLElement|object} widget - The target widget or DOM element.
192
+ * @param {object} [options]
193
+ * @param {"up"|"down"|"left"|"right"} [options.direction="up"] - The way the
194
+ * element travels as it arrives. `up` starts below its resting place.
195
+ * @param {number} [options.distance=16] - How far it travels, in framework units:
196
+ * a bare `16` is a `rem` offset through the engine, so it grows with the
197
+ * reader's font exactly like the widget it belongs to.
198
+ * @param {number} [options.duration=240] - Milliseconds; stay under 500.
199
+ * @param {string|((t: number) => number)} [options.easing="easeOut"] - Curve name
200
+ * or curve. An entrance decelerates — `easeIn` reads as a screen that is late.
201
+ * @param {boolean} [options.fade=true] - Also animate `opacity` from 0.
202
+ * @returns {() => boolean} Cancel function for both loops.
203
+ */
204
+ export const slideIn = (widget, options = {}) =>
205
+ play(widget, slideSteps(options));
206
+
207
+ /**
208
+ * The awaitable form of {@link slideIn}.
209
+ *
210
+ * @param {HTMLElement|object} widget - The target widget or DOM element.
211
+ * @param {object} [options] - See {@link slideIn}.
212
+ * @returns {Promise<HTMLElement|object>} Resolves with `widget` when the element
213
+ * has arrived, or as soon as either loop is cancelled or replaced.
214
+ */
215
+ export const slideInAsync = (widget, options = {}) =>
216
+ playAsync(widget, slideSteps(options));
217
+
218
+ /**
219
+ * The scale a `popIn` travels through, plus the optional fade.
220
+ *
221
+ * @param {object} options - See {@link popIn}.
222
+ * @returns {GestureStep[]}
223
+ */
224
+ const popSteps = ({
225
+ from = 0.94,
226
+ to = 1,
227
+ duration = 180,
228
+ easing = "easeOutBack",
229
+ fade = false,
230
+ }) => {
231
+ const steps = [{ property: "scale", from, to, duration, easing }];
232
+ if (fade) steps.push({ property: "opacity", from: 0, to: 1, duration, easing });
233
+ return steps;
234
+ };
235
+
236
+ /**
237
+ * Brings a widget in from slightly smaller than its final size.
238
+ *
239
+ * `scale` from 0 is a dot that pops, and from 1.2 it is a banner that slams.
240
+ * 0.94 → 1 with a `Back` curve is the version that reads as an object being set
241
+ * down, which is the one worth having a name for.
242
+ *
243
+ * @param {HTMLElement|object} widget - The target widget or DOM element.
244
+ * @param {object} [options]
245
+ * @param {number} [options.from=0.94] - Starting scale; keep it above 0.9.
246
+ * @param {number} [options.to=1] - Resting scale.
247
+ * @param {number} [options.duration=180] - Milliseconds.
248
+ * @param {string|((t: number) => number)} [options.easing="easeOutBack"] - Curve;
249
+ * the small overshoot is the whole effect, and a spring works here too.
250
+ * @param {boolean} [options.fade=false] - Also animate `opacity` from 0.
251
+ * @returns {() => boolean} Cancel function.
252
+ */
253
+ export const popIn = (widget, options = {}) =>
254
+ play(widget, popSteps(options));
255
+
256
+ /**
257
+ * The awaitable form of {@link popIn}.
258
+ *
259
+ * @param {HTMLElement|object} widget - The target widget or DOM element.
260
+ * @param {object} [options] - See {@link popIn}.
261
+ * @returns {Promise<HTMLElement|object>} Resolves with `widget` when it settles.
262
+ */
263
+ export const popInAsync = (widget, options = {}) =>
264
+ playAsync(widget, popSteps(options));
265
+
266
+ /**
267
+ * Says "that was wrong" without a word: a short, decaying side-to-side move.
268
+ *
269
+ * One loop with a decaying sine rather than a chain of animations, because a
270
+ * multi-phase gesture made of separate calls cannot be cancelled as a gesture —
271
+ * and a shake that outlives the error it belongs to is worse than no shake.
272
+ * `valueAt` receives eased progress, so the curve stays triangular in time.
273
+ *
274
+ * @param {HTMLElement|object} widget - The target widget or DOM element.
275
+ * @param {object} [options]
276
+ * @param {"x"|"y"} [options.axis="x"] - Which way it shakes; `y` for a vertical list.
277
+ * @param {number} [options.distance=6] - Amplitude of the first swing, in framework
278
+ * units (a bare `6` is `0.375rem`).
279
+ * @param {number} [options.cycles=3] - Number of swings; more reads as a glitch.
280
+ * @param {number} [options.duration=320] - Milliseconds.
281
+ * @returns {() => boolean} Cancel function; cancelling leaves the element at the
282
+ * offset it held at that moment, so call it from `onUnmount` rather than mid-error.
283
+ */
284
+ export const shake = (widget, options = {}) =>
285
+ play(widget, shakeSteps(options));
286
+
287
+ /**
288
+ * The awaitable form of {@link shake}.
289
+ *
290
+ * @param {HTMLElement|object} widget - The target widget or DOM element.
291
+ * @param {object} [options] - See {@link shake}.
292
+ * @returns {Promise<HTMLElement|object>} Resolves with `widget` when the shake ends.
293
+ */
294
+ export const shakeAsync = (widget, options = {}) =>
295
+ playAsync(widget, shakeSteps(options));
296
+
297
+ /**
298
+ * Moves a widget out of the screen and leaves it invisible.
299
+ *
300
+ * The exit is the mirror of {@link slideIn} and one notch shorter: motion leaving
301
+ * is a courtesy, motion arriving is information. It does not remove anything from
302
+ * the DOM — the caller hides or unmounts the widget when this resolves, which is
303
+ * the only reason to `await` an exit.
304
+ *
305
+ * @param {HTMLElement|object} widget - The target widget or DOM element.
306
+ * @param {object} [options]
307
+ * @param {"up"|"down"|"left"|"right"} [options.direction="down"] - The way it leaves.
308
+ * @param {number} [options.distance=12] - How far it travels, in framework units.
309
+ * @param {number} [options.duration=180] - Milliseconds.
310
+ * @param {string|((t: number) => number)} [options.easing="easeIn"] - Curve; an
311
+ * element leaving the screen at speed is the one place `easeIn` is right.
312
+ * @param {boolean} [options.fade=true] - Also animate `opacity` to 0.
313
+ * @returns {() => boolean} Cancel function.
314
+ */
315
+ export const slideOut = (widget, options = {}) =>
316
+ play(widget, slideSteps({ direction: "down", ...options, leaving: true }));
317
+
318
+ /**
319
+ * The awaitable form of {@link slideOut} — the usual one, since what follows an
320
+ * exit is the unmount.
321
+ *
322
+ * @param {HTMLElement|object} widget - The target widget or DOM element.
323
+ * @param {object} [options] - See {@link slideOut}.
324
+ * @returns {Promise<HTMLElement|object>} Resolves with `widget` when it has left.
325
+ */
326
+ export const slideOutAsync = (widget, options = {}) =>
327
+ playAsync(widget, slideSteps({ direction: "down", ...options, leaving: true }));
@@ -1,5 +1,4 @@
1
1
  // core/runApp.js
2
- import { stopWebRefresh } from "../utils/stopWebRefresh.js";
3
2
  import { initRouter } from "../navigations/Router.js"; // ← IMPORTANT: add this line
4
3
  import {
5
4
  applySystemTheme,
@@ -38,11 +37,6 @@ export const runApp = (
38
37
  const root = document.getElementById(rootId);
39
38
  if (!root) return;
40
39
 
41
- // If preventRefresh is true (default), we disable refreshes
42
- // if (preventRefresh) {
43
- // stopWebRefresh();
44
- // }
45
-
46
40
  let currentMainContainer = null;
47
41
  let unsubscribeTheme = null;
48
42
  let unwatchSystemTheme = null;
@@ -0,0 +1,69 @@
1
+ // helpers/boxEdges.js
2
+ /**
3
+ * @file boxEdges.js
4
+ * @description
5
+ * The one implementation behind `margin()` and `padding()`.
6
+ *
7
+ * CSS gives `margin` and `padding` the same four-value grammar, and the two
8
+ * helpers had drifted into two byte-identical copies of the side-resolution
9
+ * logic — 63 lines each, 25 branches each, differing only in the word in the
10
+ * JSDoc. A fix to the priority order or to the shorthand collapsing had to be
11
+ * made twice, so it is made here and both helpers delegate.
12
+ *
13
+ * Resolved per side, in priority order: the individual side, then its axis
14
+ * (`vertical` for top/bottom, `horizontal` for left/right), then `all`, then 0.
15
+ *
16
+ * @module helpers/boxEdges
17
+ */
18
+
19
+ /** A number becomes a px string; anything else is already CSS and passes through. */
20
+ const px = (value) => (typeof value === "number" ? `${value}px` : value);
21
+
22
+ /**
23
+ * Resolves one side of the box from the options object.
24
+ *
25
+ * @param {Object} value - The options object.
26
+ * @param {string} side - `top`, `right`, `bottom` or `left`.
27
+ * @param {string} axis - `vertical` or `horizontal`.
28
+ * @returns {number|string} The raw side value; `0` when nothing applies.
29
+ */
30
+ const sideOf = (value, side, axis) =>
31
+ value[side] !== undefined
32
+ ? value[side]
33
+ : value[axis] !== undefined
34
+ ? value[axis]
35
+ : value.all !== undefined
36
+ ? value.all
37
+ : 0;
38
+
39
+ /**
40
+ * Builds the CSS box shorthand for a margin or a padding.
41
+ *
42
+ * One value when all four sides agree, two when only the axes differ, four
43
+ * otherwise. Callers are `margin()` and `padding()`; the value they receive is
44
+ * the same string CSS would have been handed had each side been written out.
45
+ *
46
+ * @param {number|string|Object} value
47
+ * A number (converted to `px`), a raw CSS string, or an options object with
48
+ * `all`, `horizontal`, `vertical` and per-side overrides.
49
+ * @returns {string} A CSS shorthand value.
50
+ */
51
+ export const boxEdges = (value) => {
52
+ // A number or raw CSS string applies uniformly to all sides.
53
+ if (typeof value === "number" || typeof value === "string") return px(value);
54
+
55
+ if (typeof value === "object") {
56
+ const top = px(sideOf(value, "top", "vertical"));
57
+ const right = px(sideOf(value, "right", "horizontal"));
58
+ const bottom = px(sideOf(value, "bottom", "vertical"));
59
+ const left = px(sideOf(value, "left", "horizontal"));
60
+
61
+ if (top === right && right === bottom && bottom === left) return top;
62
+ if (top === bottom && right === left) return `${top} ${right}`;
63
+ return `${top} ${right} ${bottom} ${left}`;
64
+ }
65
+
66
+ return "0px";
67
+ };
68
+
69
+ export default boxEdges;
@@ -0,0 +1,39 @@
1
+ /**
2
+ * @file html.js
3
+ * @description
4
+ * The one HTML escaper in the framework.
5
+ *
6
+ * Four modules build HTML strings — the Markdown widget, the Markdown parser,
7
+ * the syntax highlighter and the Markdown code renderer — and every one of them
8
+ * used to carry its own copy of the same five `replace()` calls. Three copies of
9
+ * a security boundary is three chances for one of them to be edited alone, so
10
+ * the implementation lives here: a leaf module with no imports, which matters
11
+ * because `utils/markdownParser.js` imports six widgets and anything that
12
+ * imported it from a widget would drag all six into that widget's graph.
13
+ *
14
+ * @module helpers/html
15
+ */
16
+
17
+ /** The five characters that can end text or an attribute value early. */
18
+ const ENTITIES = {
19
+ "&": "&amp;",
20
+ "<": "&lt;",
21
+ ">": "&gt;",
22
+ '"': "&quot;",
23
+ "'": "&#39;",
24
+ };
25
+
26
+ /**
27
+ * Escapes the characters that let text break out of an HTML context.
28
+ *
29
+ * A single pass over the source: `&` is replaced once and the entities it
30
+ * produces are not rescanned, so the output is safe to insert into element
31
+ * content and into a quoted attribute value.
32
+ *
33
+ * @param {string} text - Raw text.
34
+ * @returns {string} The same text with `& < > " '` replaced by entities.
35
+ */
36
+ export const escapeHtml = (text) =>
37
+ String(text).replace(/[&<>"']/g, (char) => ENTITIES[char]);
38
+
39
+ export default escapeHtml;