pasito 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/vue.js ADDED
@@ -0,0 +1,351 @@
1
+ "use client";
2
+ "use strict";
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
11
+ var __copyProps = (to, from, except, desc) => {
12
+ if (from && typeof from === "object" || typeof from === "function") {
13
+ for (let key of __getOwnPropNames(from))
14
+ if (!__hasOwnProp.call(to, key) && key !== except)
15
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
16
+ }
17
+ return to;
18
+ };
19
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
20
+
21
+ // src/vue/index.ts
22
+ var vue_exports = {};
23
+ __export(vue_exports, {
24
+ PillStepper: () => PillStepper,
25
+ useAutoPlay: () => useAutoPlay
26
+ });
27
+ module.exports = __toCommonJS(vue_exports);
28
+
29
+ // src/vue/PillStepper.ts
30
+ var import_vue4 = require("vue");
31
+
32
+ // src/vue/composables/useStepWindow.ts
33
+ var import_vue = require("vue");
34
+
35
+ // src/core/computeStepWindow.ts
36
+ var DOT_SIZE = 8;
37
+ var ACTIVE_WIDTH = 24;
38
+ var GAP = 6;
39
+ var SLOT_SIZE = DOT_SIZE + GAP;
40
+ function computeStepWindow(count, active, maxVisible, orientation) {
41
+ if (maxVisible == null || count <= maxVisible) {
42
+ return {
43
+ windowStart: 0,
44
+ transformValue: "none",
45
+ containerSize: void 0
46
+ };
47
+ }
48
+ const half = Math.floor(maxVisible / 2);
49
+ const windowStart = Math.max(0, Math.min(active - half, count - maxVisible));
50
+ const offset = windowStart * SLOT_SIZE;
51
+ const axis = orientation === "vertical" ? "Y" : "X";
52
+ const transformValue = `translate${axis}(-${offset}px)`;
53
+ const size = (maxVisible - 1) * DOT_SIZE + ACTIVE_WIDTH + (maxVisible - 1) * GAP;
54
+ return { windowStart, transformValue, containerSize: size };
55
+ }
56
+
57
+ // src/vue/composables/useStepWindow.ts
58
+ function useStepWindow(props) {
59
+ return (0, import_vue.computed)(
60
+ () => computeStepWindow(
61
+ props.count.value,
62
+ props.active.value,
63
+ props.maxVisible.value,
64
+ props.orientation.value
65
+ )
66
+ );
67
+ }
68
+
69
+ // src/vue/composables/useAnimatingSteps.ts
70
+ var import_vue2 = require("vue");
71
+
72
+ // src/core/StepAnimator.ts
73
+ var StepAnimator = class {
74
+ constructor(count) {
75
+ this.keyGen = count;
76
+ this.steps = Array.from({ length: count }, (_, i) => ({
77
+ key: i,
78
+ index: i,
79
+ phase: "stable"
80
+ }));
81
+ }
82
+ reconcile(newCount) {
83
+ const liveCount = this.steps.filter((s) => s.phase !== "exiting").length;
84
+ if (liveCount === newCount) {
85
+ return {
86
+ hasEntering: this.steps.some((s) => s.phase === "entering"),
87
+ exitingCount: this.steps.filter((s) => s.phase === "exiting").length
88
+ };
89
+ }
90
+ if (newCount < liveCount) {
91
+ let seen = 0;
92
+ this.steps = this.steps.map((s) => {
93
+ if (s.phase === "exiting") return s;
94
+ seen++;
95
+ if (seen > newCount) return { ...s, phase: "exiting" };
96
+ return s;
97
+ });
98
+ }
99
+ if (newCount > liveCount) {
100
+ for (let i = liveCount; i < newCount; i++) {
101
+ this.keyGen++;
102
+ this.steps.push({
103
+ key: this.keyGen,
104
+ index: i,
105
+ phase: "entering"
106
+ });
107
+ }
108
+ }
109
+ let idx = 0;
110
+ this.steps = this.steps.map(
111
+ (s) => s.phase === "exiting" ? s : { ...s, index: idx++ }
112
+ );
113
+ return {
114
+ hasEntering: this.steps.some((s) => s.phase === "entering"),
115
+ exitingCount: this.steps.filter((s) => s.phase === "exiting").length
116
+ };
117
+ }
118
+ promoteEntering() {
119
+ this.steps = this.steps.map(
120
+ (s) => s.phase === "entering" ? { ...s, phase: "stable" } : s
121
+ );
122
+ }
123
+ removeExiting() {
124
+ this.steps = this.steps.filter((s) => s.phase !== "exiting");
125
+ }
126
+ getSteps() {
127
+ return [...this.steps];
128
+ }
129
+ };
130
+
131
+ // src/vue/composables/useAnimatingSteps.ts
132
+ function useAnimatingSteps(count, _duration) {
133
+ const animator = new StepAnimator(count.value);
134
+ const steps = (0, import_vue2.ref)(animator.getSteps());
135
+ (0, import_vue2.watch)(count, (newCount) => {
136
+ animator.reconcile(newCount);
137
+ steps.value = animator.getSteps();
138
+ });
139
+ const hasEntering = (0, import_vue2.computed)(() => steps.value.some((s) => s.phase === "entering"));
140
+ (0, import_vue2.watch)(hasEntering, (val, _, onCleanup) => {
141
+ if (!val) return;
142
+ let raf1;
143
+ let raf2;
144
+ raf1 = requestAnimationFrame(() => {
145
+ raf2 = requestAnimationFrame(() => {
146
+ animator.promoteEntering();
147
+ steps.value = animator.getSteps();
148
+ });
149
+ });
150
+ onCleanup(() => {
151
+ cancelAnimationFrame(raf1);
152
+ cancelAnimationFrame(raf2);
153
+ });
154
+ });
155
+ const exitingCount = (0, import_vue2.computed)(
156
+ () => steps.value.filter((s) => s.phase === "exiting").length
157
+ );
158
+ (0, import_vue2.watch)(exitingCount, (val, _, onCleanup) => {
159
+ if (val === 0) return;
160
+ const timer = setTimeout(() => {
161
+ animator.removeExiting();
162
+ steps.value = animator.getSteps();
163
+ }, 300);
164
+ onCleanup(() => clearTimeout(timer));
165
+ });
166
+ return steps;
167
+ }
168
+
169
+ // src/vue/PillStep.ts
170
+ var import_vue3 = require("vue");
171
+ var PillStep = (0, import_vue3.defineComponent)({
172
+ name: "PillStep",
173
+ props: {
174
+ index: { type: Number, required: true },
175
+ isActive: { type: Boolean, required: true },
176
+ phase: {
177
+ type: String,
178
+ required: true
179
+ },
180
+ transitionDuration: { type: Number, required: true },
181
+ filling: { type: Boolean, default: false },
182
+ fillDuration: { type: Number, default: void 0 }
183
+ },
184
+ emits: ["click"],
185
+ setup(props, { emit }) {
186
+ return () => {
187
+ const classNames = [
188
+ "pasito-step",
189
+ props.isActive && "pasito-step-active",
190
+ props.isActive && props.filling && "pasito-step-filling",
191
+ props.phase === "entering" && "pasito-entering",
192
+ props.phase === "exiting" && "pasito-exiting"
193
+ ].filter(Boolean).join(" ");
194
+ const style = {
195
+ "--pill-duration": `${props.transitionDuration}ms`
196
+ };
197
+ if (props.isActive && props.filling && props.fillDuration) {
198
+ style["--pill-fill-duration"] = `${props.fillDuration}ms`;
199
+ }
200
+ return (0, import_vue3.h)("button", {
201
+ class: classNames,
202
+ style,
203
+ onClick: () => emit("click"),
204
+ role: "tab",
205
+ "aria-selected": props.isActive,
206
+ "aria-label": `Step ${props.index + 1}`,
207
+ tabindex: props.isActive ? 0 : -1
208
+ });
209
+ };
210
+ }
211
+ });
212
+
213
+ // src/vue/PillStepper.ts
214
+ var PillStepper = (0, import_vue4.defineComponent)({
215
+ name: "PillStepper",
216
+ props: {
217
+ count: { type: Number, required: true },
218
+ active: { type: Number, required: true },
219
+ orientation: {
220
+ type: String,
221
+ default: "horizontal"
222
+ },
223
+ maxVisible: { type: Number, default: void 0 },
224
+ transitionDuration: { type: Number, default: 500 },
225
+ easing: { type: String, default: void 0 },
226
+ filling: { type: Boolean, default: false },
227
+ fillDuration: { type: Number, default: void 0 }
228
+ },
229
+ emits: ["step-click"],
230
+ setup(props, { emit }) {
231
+ const stepWindow = useStepWindow({
232
+ count: (0, import_vue4.toRef)(props, "count"),
233
+ active: (0, import_vue4.toRef)(props, "active"),
234
+ maxVisible: (0, import_vue4.toRef)(props, "maxVisible"),
235
+ orientation: (0, import_vue4.toRef)(props, "orientation")
236
+ });
237
+ const steps = useAnimatingSteps(
238
+ (0, import_vue4.toRef)(props, "count"),
239
+ (0, import_vue4.toRef)(props, "transitionDuration")
240
+ );
241
+ const containerClass = (0, import_vue4.computed)(
242
+ () => [
243
+ "pasito-container",
244
+ props.orientation === "vertical" && "pasito-vertical"
245
+ ].filter(Boolean).join(" ")
246
+ );
247
+ return () => {
248
+ const { containerSize, transformValue } = stepWindow.value;
249
+ const sizeStyle = {};
250
+ if (containerSize != null) {
251
+ if (props.orientation === "vertical") {
252
+ sizeStyle.height = `${containerSize}px`;
253
+ } else {
254
+ sizeStyle.width = `${containerSize}px`;
255
+ }
256
+ }
257
+ return (0, import_vue4.h)(
258
+ "div",
259
+ {
260
+ class: containerClass.value,
261
+ role: "tablist",
262
+ "aria-label": "Progress steps",
263
+ style: {
264
+ "--pill-duration": `${props.transitionDuration}ms`,
265
+ ...props.easing && { "--pill-easing": props.easing },
266
+ ...sizeStyle
267
+ }
268
+ },
269
+ [
270
+ (0, import_vue4.h)(
271
+ "div",
272
+ {
273
+ class: "pasito-track",
274
+ style: { transform: transformValue }
275
+ },
276
+ steps.value.map(
277
+ (step) => (0, import_vue4.h)(PillStep, {
278
+ key: step.key,
279
+ index: step.index,
280
+ isActive: step.index === props.active,
281
+ phase: step.phase,
282
+ transitionDuration: props.transitionDuration,
283
+ filling: step.index === props.active && props.filling,
284
+ fillDuration: props.fillDuration,
285
+ onClick: () => emit("step-click", step.index)
286
+ })
287
+ )
288
+ )
289
+ ]
290
+ );
291
+ };
292
+ }
293
+ });
294
+
295
+ // src/vue/composables/useAutoPlay.ts
296
+ var import_vue5 = require("vue");
297
+
298
+ // src/core/AutoPlayController.ts
299
+ var AutoPlayController = class {
300
+ constructor({ stepDuration = 3e3, loop = true } = {}) {
301
+ this.stepDuration = stepDuration;
302
+ this.loop = loop;
303
+ }
304
+ computeNext(active, count) {
305
+ return active < count - 1 ? active + 1 : this.loop ? 0 : null;
306
+ }
307
+ };
308
+
309
+ // src/vue/composables/useAutoPlay.ts
310
+ function useAutoPlay(options) {
311
+ const playing = (0, import_vue5.ref)(false);
312
+ const controller = new AutoPlayController();
313
+ const stepDuration = (0, import_vue5.computed)(() => (0, import_vue5.unref)(options.stepDuration) ?? 3e3);
314
+ const loop = (0, import_vue5.computed)(() => (0, import_vue5.unref)(options.loop) ?? true);
315
+ const enabled = (0, import_vue5.computed)(() => (0, import_vue5.unref)(options.enabled) ?? true);
316
+ (0, import_vue5.watch)(enabled, (val) => {
317
+ if (!val) playing.value = false;
318
+ });
319
+ (0, import_vue5.watch)(
320
+ [playing, enabled, options.active, options.count, stepDuration, loop],
321
+ ([isPlaying, isEnabled, active, count, dur, lp], _, onCleanup) => {
322
+ if (!isPlaying || !isEnabled) return;
323
+ controller.stepDuration = dur;
324
+ controller.loop = lp;
325
+ const timer = setTimeout(() => {
326
+ const next = controller.computeNext(active, count);
327
+ if (next !== null) {
328
+ options.onStepChange(next);
329
+ } else {
330
+ playing.value = false;
331
+ }
332
+ }, dur);
333
+ onCleanup(() => clearTimeout(timer));
334
+ },
335
+ { immediate: true }
336
+ );
337
+ const isActive = (0, import_vue5.computed)(() => playing.value && enabled.value);
338
+ return {
339
+ playing: (0, import_vue5.computed)(() => isActive.value),
340
+ toggle: () => {
341
+ playing.value = !playing.value;
342
+ },
343
+ filling: (0, import_vue5.computed)(() => isActive.value),
344
+ fillDuration: stepDuration
345
+ };
346
+ }
347
+ // Annotate the CommonJS export names for ESM import in node:
348
+ 0 && (module.exports = {
349
+ PillStepper,
350
+ useAutoPlay
351
+ });
package/dist/vue.mjs ADDED
@@ -0,0 +1,231 @@
1
+ "use client";
2
+ import {
3
+ AutoPlayController,
4
+ StepAnimator,
5
+ computeStepWindow
6
+ } from "./chunk-4JH2WF3D.mjs";
7
+
8
+ // src/vue/PillStepper.ts
9
+ import { defineComponent as defineComponent2, toRef, computed as computed3, h as h2 } from "vue";
10
+
11
+ // src/vue/composables/useStepWindow.ts
12
+ import { computed } from "vue";
13
+ function useStepWindow(props) {
14
+ return computed(
15
+ () => computeStepWindow(
16
+ props.count.value,
17
+ props.active.value,
18
+ props.maxVisible.value,
19
+ props.orientation.value
20
+ )
21
+ );
22
+ }
23
+
24
+ // src/vue/composables/useAnimatingSteps.ts
25
+ import { ref, watch, computed as computed2 } from "vue";
26
+ function useAnimatingSteps(count, _duration) {
27
+ const animator = new StepAnimator(count.value);
28
+ const steps = ref(animator.getSteps());
29
+ watch(count, (newCount) => {
30
+ animator.reconcile(newCount);
31
+ steps.value = animator.getSteps();
32
+ });
33
+ const hasEntering = computed2(() => steps.value.some((s) => s.phase === "entering"));
34
+ watch(hasEntering, (val, _, onCleanup) => {
35
+ if (!val) return;
36
+ let raf1;
37
+ let raf2;
38
+ raf1 = requestAnimationFrame(() => {
39
+ raf2 = requestAnimationFrame(() => {
40
+ animator.promoteEntering();
41
+ steps.value = animator.getSteps();
42
+ });
43
+ });
44
+ onCleanup(() => {
45
+ cancelAnimationFrame(raf1);
46
+ cancelAnimationFrame(raf2);
47
+ });
48
+ });
49
+ const exitingCount = computed2(
50
+ () => steps.value.filter((s) => s.phase === "exiting").length
51
+ );
52
+ watch(exitingCount, (val, _, onCleanup) => {
53
+ if (val === 0) return;
54
+ const timer = setTimeout(() => {
55
+ animator.removeExiting();
56
+ steps.value = animator.getSteps();
57
+ }, 300);
58
+ onCleanup(() => clearTimeout(timer));
59
+ });
60
+ return steps;
61
+ }
62
+
63
+ // src/vue/PillStep.ts
64
+ import { defineComponent, h } from "vue";
65
+ var PillStep = defineComponent({
66
+ name: "PillStep",
67
+ props: {
68
+ index: { type: Number, required: true },
69
+ isActive: { type: Boolean, required: true },
70
+ phase: {
71
+ type: String,
72
+ required: true
73
+ },
74
+ transitionDuration: { type: Number, required: true },
75
+ filling: { type: Boolean, default: false },
76
+ fillDuration: { type: Number, default: void 0 }
77
+ },
78
+ emits: ["click"],
79
+ setup(props, { emit }) {
80
+ return () => {
81
+ const classNames = [
82
+ "pasito-step",
83
+ props.isActive && "pasito-step-active",
84
+ props.isActive && props.filling && "pasito-step-filling",
85
+ props.phase === "entering" && "pasito-entering",
86
+ props.phase === "exiting" && "pasito-exiting"
87
+ ].filter(Boolean).join(" ");
88
+ const style = {
89
+ "--pill-duration": `${props.transitionDuration}ms`
90
+ };
91
+ if (props.isActive && props.filling && props.fillDuration) {
92
+ style["--pill-fill-duration"] = `${props.fillDuration}ms`;
93
+ }
94
+ return h("button", {
95
+ class: classNames,
96
+ style,
97
+ onClick: () => emit("click"),
98
+ role: "tab",
99
+ "aria-selected": props.isActive,
100
+ "aria-label": `Step ${props.index + 1}`,
101
+ tabindex: props.isActive ? 0 : -1
102
+ });
103
+ };
104
+ }
105
+ });
106
+
107
+ // src/vue/PillStepper.ts
108
+ var PillStepper = defineComponent2({
109
+ name: "PillStepper",
110
+ props: {
111
+ count: { type: Number, required: true },
112
+ active: { type: Number, required: true },
113
+ orientation: {
114
+ type: String,
115
+ default: "horizontal"
116
+ },
117
+ maxVisible: { type: Number, default: void 0 },
118
+ transitionDuration: { type: Number, default: 500 },
119
+ easing: { type: String, default: void 0 },
120
+ filling: { type: Boolean, default: false },
121
+ fillDuration: { type: Number, default: void 0 }
122
+ },
123
+ emits: ["step-click"],
124
+ setup(props, { emit }) {
125
+ const stepWindow = useStepWindow({
126
+ count: toRef(props, "count"),
127
+ active: toRef(props, "active"),
128
+ maxVisible: toRef(props, "maxVisible"),
129
+ orientation: toRef(props, "orientation")
130
+ });
131
+ const steps = useAnimatingSteps(
132
+ toRef(props, "count"),
133
+ toRef(props, "transitionDuration")
134
+ );
135
+ const containerClass = computed3(
136
+ () => [
137
+ "pasito-container",
138
+ props.orientation === "vertical" && "pasito-vertical"
139
+ ].filter(Boolean).join(" ")
140
+ );
141
+ return () => {
142
+ const { containerSize, transformValue } = stepWindow.value;
143
+ const sizeStyle = {};
144
+ if (containerSize != null) {
145
+ if (props.orientation === "vertical") {
146
+ sizeStyle.height = `${containerSize}px`;
147
+ } else {
148
+ sizeStyle.width = `${containerSize}px`;
149
+ }
150
+ }
151
+ return h2(
152
+ "div",
153
+ {
154
+ class: containerClass.value,
155
+ role: "tablist",
156
+ "aria-label": "Progress steps",
157
+ style: {
158
+ "--pill-duration": `${props.transitionDuration}ms`,
159
+ ...props.easing && { "--pill-easing": props.easing },
160
+ ...sizeStyle
161
+ }
162
+ },
163
+ [
164
+ h2(
165
+ "div",
166
+ {
167
+ class: "pasito-track",
168
+ style: { transform: transformValue }
169
+ },
170
+ steps.value.map(
171
+ (step) => h2(PillStep, {
172
+ key: step.key,
173
+ index: step.index,
174
+ isActive: step.index === props.active,
175
+ phase: step.phase,
176
+ transitionDuration: props.transitionDuration,
177
+ filling: step.index === props.active && props.filling,
178
+ fillDuration: props.fillDuration,
179
+ onClick: () => emit("step-click", step.index)
180
+ })
181
+ )
182
+ )
183
+ ]
184
+ );
185
+ };
186
+ }
187
+ });
188
+
189
+ // src/vue/composables/useAutoPlay.ts
190
+ import { ref as ref2, computed as computed4, watch as watch2, unref } from "vue";
191
+ function useAutoPlay(options) {
192
+ const playing = ref2(false);
193
+ const controller = new AutoPlayController();
194
+ const stepDuration = computed4(() => unref(options.stepDuration) ?? 3e3);
195
+ const loop = computed4(() => unref(options.loop) ?? true);
196
+ const enabled = computed4(() => unref(options.enabled) ?? true);
197
+ watch2(enabled, (val) => {
198
+ if (!val) playing.value = false;
199
+ });
200
+ watch2(
201
+ [playing, enabled, options.active, options.count, stepDuration, loop],
202
+ ([isPlaying, isEnabled, active, count, dur, lp], _, onCleanup) => {
203
+ if (!isPlaying || !isEnabled) return;
204
+ controller.stepDuration = dur;
205
+ controller.loop = lp;
206
+ const timer = setTimeout(() => {
207
+ const next = controller.computeNext(active, count);
208
+ if (next !== null) {
209
+ options.onStepChange(next);
210
+ } else {
211
+ playing.value = false;
212
+ }
213
+ }, dur);
214
+ onCleanup(() => clearTimeout(timer));
215
+ },
216
+ { immediate: true }
217
+ );
218
+ const isActive = computed4(() => playing.value && enabled.value);
219
+ return {
220
+ playing: computed4(() => isActive.value),
221
+ toggle: () => {
222
+ playing.value = !playing.value;
223
+ },
224
+ filling: computed4(() => isActive.value),
225
+ fillDuration: stepDuration
226
+ };
227
+ }
228
+ export {
229
+ PillStepper,
230
+ useAutoPlay
231
+ };
package/package.json ADDED
@@ -0,0 +1,51 @@
1
+ {
2
+ "name": "pasito",
3
+ "version": "0.1.0",
4
+ "description": "A dependency-free stepper/progress indicator with pure CSS transitions — React & Vue",
5
+ "main": "./dist/index.js",
6
+ "module": "./dist/index.mjs",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.mjs",
12
+ "require": "./dist/index.js"
13
+ },
14
+ "./react": {
15
+ "types": "./dist/react.d.ts",
16
+ "import": "./dist/react.mjs",
17
+ "require": "./dist/react.js"
18
+ },
19
+ "./vue": {
20
+ "types": "./dist/vue.d.ts",
21
+ "import": "./dist/vue.mjs",
22
+ "require": "./dist/vue.js"
23
+ },
24
+ "./styles.css": "./dist/index.css"
25
+ },
26
+ "files": ["dist"],
27
+ "sideEffects": ["*.css"],
28
+ "scripts": {
29
+ "build": "tsup",
30
+ "dev": "tsup --watch"
31
+ },
32
+ "peerDependencies": {
33
+ "react": ">=18",
34
+ "react-dom": ">=18",
35
+ "vue": ">=3"
36
+ },
37
+ "peerDependenciesMeta": {
38
+ "react": { "optional": true },
39
+ "react-dom": { "optional": true },
40
+ "vue": { "optional": true }
41
+ },
42
+ "devDependencies": {
43
+ "react": "^19.0.0",
44
+ "react-dom": "^19.0.0",
45
+ "@types/react": "^19.0.0",
46
+ "@types/react-dom": "^19.0.0",
47
+ "vue": "^3.5.0",
48
+ "tsup": "^8.0.0",
49
+ "typescript": "^5.0.0"
50
+ }
51
+ }