@studiocubics/utils 0.0.1 → 0.0.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.
package/package.json CHANGED
@@ -5,17 +5,17 @@
5
5
  "access": "public"
6
6
  },
7
7
  "private": false,
8
- "version": "0.0.1",
8
+ "version": "0.0.2",
9
9
  "main": "./dist/index.js",
10
10
  "exports": {
11
11
  ".": "./dist/index.js"
12
12
  },
13
13
  "types": "./dist/index.d.ts",
14
14
  "peerDependencies": {
15
- "react": "19",
16
- "@types/react": "19"
15
+ "react": ">= 19"
17
16
  },
18
17
  "devDependencies": {
18
+ "@types/react": "^19",
19
19
  "tsup": "^8.5.1"
20
20
  },
21
21
  "keywords": [
@@ -1,17 +0,0 @@
1
-
2
- > @studiocubics/utils@0.0.1 build /home/runner/work/StudioCubics/StudioCubics/packages/utils
3
- > tsup
4
-
5
- CLI Building entry: src/index.ts
6
- CLI Using tsconfig: tsconfig.json
7
- CLI tsup v8.5.1
8
- CLI Using tsup config: /home/runner/work/StudioCubics/StudioCubics/packages/utils/tsup.config.ts
9
- CLI Target: es2022
10
- CLI Cleaning output folder
11
- CJS Build start
12
- CJS dist/index.js 9.75 KB
13
- CJS dist/index.js.map 19.24 KB
14
- CJS ⚡️ Build success in 343ms
15
- DTS Build start
16
- DTS ⚡️ Build success in 2850ms
17
- DTS dist/index.d.ts 5.81 KB
package/CHANGELOG.md DELETED
@@ -1,7 +0,0 @@
1
- # @studiocubics/utils
2
-
3
- ## 0.0.1
4
-
5
- ### Patch Changes
6
-
7
- - First publish inshallah!
package/src/API/_index.ts DELETED
@@ -1 +0,0 @@
1
- export * from "./apiRes";
package/src/API/apiRes.ts DELETED
@@ -1,74 +0,0 @@
1
- export type ActionResponse<K extends string = string, E extends K[] = K[]> = {
2
- success: boolean;
3
- payload?: unknown;
4
- error?: string;
5
- fieldErrors?: Record<K, E>;
6
- };
7
-
8
- export const apiRes = {
9
- actionFail: <K extends string = string>(
10
- error: ActionResponse<K>["error"],
11
- fieldErrors?: Record<string, string[]>
12
- ): ActionResponse<K> => ({
13
- success: false,
14
- error,
15
- fieldErrors: fieldErrors as ActionResponse<K>["fieldErrors"],
16
- }),
17
- actionSuccess: <K extends string = string>(
18
- payload?: unknown
19
- ): ActionResponse<K> => ({
20
- success: true,
21
- payload,
22
- }),
23
- /**
24
- * "Failed to fetch data!"
25
- */
26
- apiError: (message?: string) => (message ? message : "Failed to fetch data!"),
27
- /**
28
- * "You are not authenticated to access this resource."
29
- */
30
- unauthorised: "You are not authenticated to access this resource.",
31
- /**
32
- * "You do not have the necessary permissions to access this resource."
33
- */
34
- forbidden:
35
- "You do not have the necessary permissions to access this resource.",
36
- /**
37
- * "${item} is of wrong type, expected type: ${expectedType}"
38
- */
39
- wrongType: (item: string, expectedType?: string) =>
40
- `${item} is of wrong type${expectedType ? `, expected type: ${expectedType}` : ""}.`,
41
- /**
42
- * "An error occured trying to read the formdata in these fields:
43
- * ${fields}."
44
- */
45
- formdataError: (fields: string | string[]) =>
46
- `An error occured trying to read the formdata in these fields:\n${JSON.stringify(fields)}.`,
47
- /**
48
- * "Missing parameter: ${param}."
49
- */
50
- missingParams: (param: string | string[]) =>
51
- `Missing parameter: ${JSON.stringify(param)}.`,
52
- /**
53
- * "${item} not found!"
54
- */
55
- notFound: (item: string) => `${item} not found!`,
56
- /**
57
- * "${item} already exists!"
58
- */
59
- alreadyExists: (item: string) => `${item} already exists!`,
60
- /**
61
- * "${contentName} size exceeded the maximum of ${maxSize}"
62
- */
63
- contentTooLarge: (contentName: string, maxSize?: string) =>
64
- `${contentName} size exceeded${
65
- maxSize ? ` the maximum of ${maxSize}` : ""
66
- }.`,
67
- /**
68
- * An API Error has occurred:
69
- * Check here => ${location}
70
- * More Info: ${errorMessage}
71
- */
72
- generalError: (location: string, errorMessage: string = "") =>
73
- `An API Error has occurred:\nCheck here => ${location}\nMore Info: ${errorMessage}`,
74
- };
@@ -1 +0,0 @@
1
- export * from "./calculateSafePosition";
@@ -1,137 +0,0 @@
1
- type VerticalOrigin = "top" | "center" | "bottom";
2
- type HorizontalOrigin = "left" | "center" | "right";
3
-
4
- interface ParsedOrigin {
5
- vertical: VerticalOrigin;
6
- horizontal: HorizontalOrigin;
7
- }
8
-
9
- export interface SafePositionOptions {
10
- anchorOrigin?: string;
11
- transformOrigin?: string;
12
- margin?: number;
13
- }
14
-
15
- export interface Position {
16
- x: number;
17
- y: number;
18
- }
19
-
20
- /**
21
- * Calculates safe position of an element within viewport relative to an anchor element
22
- * Works like Material UI Popover positioning:
23
- * - anchorOrigin: point on the anchor element (e.g., "top center" = top-center of anchor)
24
- * - transformOrigin: point on the positioned element that attaches to anchor (e.g., "bottom center" = bottom-center of popup attaches to anchor point)
25
- *
26
- * Example: anchorOrigin "top center" + transformOrigin "bottom center" positions popup above and centered
27
- *
28
- * @param element - The element to position
29
- * @param anchorElement - The anchor element to position relative to
30
- * @param options - Configuration options
31
- * @returns Safe x, y coordinates
32
- */
33
- export function calculateSafePosition(
34
- element: HTMLElement | null,
35
- anchorElement: HTMLElement | null,
36
- options: SafePositionOptions = {}
37
- ): Position {
38
- const {
39
- anchorOrigin = "top left",
40
- transformOrigin = "top left",
41
- margin = 0,
42
- } = options;
43
-
44
- if (!element || !anchorElement) return { x: 0, y: 0 };
45
-
46
- const parseOrigin = (origin: string): ParsedOrigin => {
47
- const parts = origin.toLowerCase().trim().split(/\s+/);
48
- let vertical: VerticalOrigin = "top";
49
- let horizontal: HorizontalOrigin = "left";
50
-
51
- for (const part of parts) {
52
- if (["top", "bottom"].includes(part)) vertical = part as VerticalOrigin;
53
- else if (["left", "right"].includes(part))
54
- horizontal = part as HorizontalOrigin;
55
- else if (part === "center") {
56
- // Assign center depending on position in string
57
- if (parts.length === 1) {
58
- vertical = "center";
59
- horizontal = "center";
60
- } else if (["top", "bottom"].includes(parts[0])) horizontal = "center";
61
- else vertical = "center";
62
- }
63
- }
64
-
65
- return { vertical, horizontal };
66
- };
67
-
68
- const getAnchorOffset = (
69
- size: number,
70
- origin: VerticalOrigin | HorizontalOrigin
71
- ): number => {
72
- if (origin === "center") return size / 2;
73
- if (origin === "bottom" || origin === "right") return size;
74
- return 0; // top or left
75
- };
76
-
77
- const getTransformOffset = (
78
- size: number,
79
- origin: VerticalOrigin | HorizontalOrigin
80
- ): number => {
81
- if (origin === "center") return size / 2;
82
- if (origin === "bottom" || origin === "right") return size;
83
- return 0; // top or left
84
- };
85
-
86
- const elementRect = element.getBoundingClientRect();
87
- const anchorRect = anchorElement.getBoundingClientRect();
88
- const viewportWidth = window.innerWidth;
89
- const viewportHeight = window.innerHeight;
90
-
91
- const anchor = parseOrigin(anchorOrigin);
92
- const transform = parseOrigin(transformOrigin);
93
-
94
- // Calculate anchor point on the anchor element
95
- const anchorX =
96
- anchorRect.left + getAnchorOffset(anchorRect.width, anchor.horizontal);
97
- const anchorY =
98
- anchorRect.top + getAnchorOffset(anchorRect.height, anchor.vertical);
99
-
100
- // Calculate where on the element we're attaching (transform origin point)
101
- const elementOriginX = getTransformOffset(
102
- elementRect.width,
103
- transform.horizontal
104
- );
105
- const elementOriginY = getTransformOffset(
106
- elementRect.height,
107
- transform.vertical
108
- );
109
-
110
- // Position the element so its transform origin aligns with the anchor point
111
- let x = anchorX - elementOriginX;
112
- let y = anchorY - elementOriginY;
113
-
114
- // Check and adjust for viewport boundaries with margin
115
- // Right boundary
116
- if (x + elementRect.width > viewportWidth - margin) {
117
- x = viewportWidth - elementRect.width - margin;
118
- }
119
- // Left boundary
120
- if (x < margin) {
121
- x = margin;
122
- }
123
-
124
- // Bottom boundary
125
- if (y + elementRect.height > viewportHeight - margin) {
126
- y = viewportHeight - elementRect.height - margin;
127
- }
128
- // Top boundary
129
- if (y < margin) {
130
- y = margin;
131
- }
132
-
133
- return {
134
- x: Math.max(margin, x),
135
- y: Math.max(margin, y),
136
- };
137
- }
@@ -1 +0,0 @@
1
- export * from "./delay";
package/src/Code/delay.ts DELETED
@@ -1,5 +0,0 @@
1
- /**
2
- * Delays the program loop for the given amount of milliseconds
3
- */
4
- export const delay = (ms: number) =>
5
- new Promise((resolve) => setTimeout(resolve, ms));
@@ -1,2 +0,0 @@
1
- export * from "./relativeTime";
2
- export * from "./formateDate";
@@ -1,140 +0,0 @@
1
- type DateToken =
2
- | "Day"
3
- | "day"
4
- | "DD"
5
- | "D"
6
- | "MMMM"
7
- | "MMM"
8
- | "MM"
9
- | "YYYY"
10
- | "YY"
11
- | "HH"
12
- | "H"
13
- | "hh"
14
- | "h"
15
- | "mm"
16
- | "m"
17
- | "ss"
18
- | "s"
19
- | "A"
20
- | "a";
21
-
22
- /**
23
- * Formats a date according to the provided format string.
24
- *
25
- * @param dateInput - The date to format (as Date object, timestamp, or date string)
26
- * @param format - The format string using tokens (default: "day D MMM, YYYY")
27
- *
28
- * @returns The formatted date string
29
- *
30
- * @example
31
- * formatDate(new Date(), "Day, DD MMMM YYYY")
32
- * // => "Thursday, 02 January 2026"
33
- *
34
- * @example
35
- * formatDate(new Date(), "DD/MM/YYYY HH:mm:ss")
36
- * // => "02/01/2026 14:30:45"
37
- *
38
- * @example
39
- * formatDate(new Date(), "h:mm A")
40
- * // => "2:30 PM"
41
- *
42
- * Available tokens:
43
- * - Day: Full day name (e.g., "Monday")
44
- * - day: Short day name (e.g., "Mon")
45
- * - DD: Day of month, zero-padded (e.g., "05")
46
- * - D: Day of month (e.g., "5")
47
- * - MMMM: Full month name (e.g., "January")
48
- * - MMM: Short month name (e.g., "Jan")
49
- * - MM: Month number, zero-padded (e.g., "01")
50
- * - YYYY: Full year (e.g., "2026")
51
- * - YY: Two-digit year (e.g., "26")
52
- * - HH: Hours (24-hour), zero-padded (e.g., "14")
53
- * - H: Hours (24-hour) (e.g., "14")
54
- * - hh: Hours (12-hour), zero-padded (e.g., "02")
55
- * - h: Hours (12-hour) (e.g., "2")
56
- * - mm: Minutes, zero-padded (e.g., "05")
57
- * - m: Minutes (e.g., "5")
58
- * - ss: Seconds, zero-padded (e.g., "09")
59
- * - s: Seconds (e.g., "9")
60
- * - A: AM/PM uppercase (e.g., "PM")
61
- * - a: am/pm lowercase (e.g., "pm")
62
- */
63
- export function formatDate(
64
- dateInput: number | string | Date,
65
- format: string = "HH:mm:ss day D MMM, YYYY"
66
- ) {
67
- const date = new Date(dateInput);
68
-
69
- const pad = (n: number) => String(n).padStart(2, "0");
70
-
71
- const daysFull = [
72
- "Sunday",
73
- "Monday",
74
- "Tuesday",
75
- "Wednesday",
76
- "Thursday",
77
- "Friday",
78
- "Saturday",
79
- ];
80
- const daysShort = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
81
- const monthsFull = [
82
- "January",
83
- "February",
84
- "March",
85
- "April",
86
- "May",
87
- "June",
88
- "July",
89
- "August",
90
- "September",
91
- "October",
92
- "November",
93
- "December",
94
- ];
95
- const monthsShort = monthsFull.map((m) => m.slice(0, 3));
96
-
97
- // Extract all values once
98
- const dayIndex = date.getDay();
99
- const dateNum = date.getDate();
100
- const monthIndex = date.getMonth();
101
- const year = date.getFullYear();
102
- const hours24 = date.getHours();
103
- const minutes = date.getMinutes();
104
- const seconds = date.getSeconds();
105
-
106
- const hours12 = hours24 % 12 || 12;
107
- const isPM = hours24 >= 12;
108
- const yearStr = String(year);
109
-
110
- const replacements: Record<DateToken, string> = {
111
- Day: daysFull[dayIndex],
112
- day: daysShort[dayIndex],
113
- DD: pad(dateNum),
114
- D: String(dateNum),
115
- MMMM: monthsFull[monthIndex],
116
- MMM: monthsShort[monthIndex],
117
- MM: pad(monthIndex + 1),
118
- YYYY: yearStr,
119
- YY: yearStr.slice(-2),
120
- HH: pad(hours24),
121
- H: String(hours24),
122
- hh: pad(hours12),
123
- h: String(hours12),
124
- mm: pad(minutes),
125
- m: String(minutes),
126
- ss: pad(seconds),
127
- s: String(seconds),
128
- A: isPM ? "PM" : "AM",
129
- a: isPM ? "pm" : "am",
130
- };
131
-
132
- // Sort tokens by length (longest first) to avoid partial replacements
133
- const tokens = (Object.keys(replacements) as DateToken[]).sort(
134
- (a, b) => b.length - a.length
135
- );
136
-
137
- // Use a single regex replacement to avoid overlapping matches
138
- const pattern = new RegExp(tokens.join("|"), "g");
139
- return format.replace(pattern, (match) => replacements[match as DateToken]);
140
- }
@@ -1,64 +0,0 @@
1
- /**
2
- * Returns a human-readable relative time string vs Date.now().
3
- * Accepts Date | number (ms) | date-string.
4
- * Fast, allocation-light, no Intl.
5
- */
6
- export function relativeTime(
7
- input: Date | number | string
8
- ): string {
9
- const t: number =
10
- input instanceof Date
11
- ? input.getTime()
12
- : typeof input === "number"
13
- ? input
14
- : Date.parse(input);
15
-
16
- if (!Number.isFinite(t)) return "invalid date";
17
-
18
- const now = Date.now();
19
- const diffMs = t - now;
20
- const absMs = Math.abs(diffMs);
21
- const isFuture = diffMs > 0;
22
-
23
- // time constants (ms)
24
- const MIN = 60_000;
25
- const HOUR = 3_600_000;
26
- const DAY = 86_400_000;
27
- const WEEK = 604_800_000;
28
- const YEAR = 31_536_000_000;
29
-
30
- // very far away → coarse wording
31
- if (absMs > 5 * YEAR) {
32
- return isFuture ? "a long time from now" : "a long time ago";
33
- }
34
-
35
- if (absMs < MIN) {
36
- return "just now";
37
- }
38
-
39
- let value: number;
40
- let unit: "minute" | "hour" | "day" | "week" | "year";
41
-
42
- if (absMs < HOUR) {
43
- value = Math.round(absMs / MIN);
44
- unit = "minute";
45
- } else if (absMs < DAY) {
46
- value = Math.round(absMs / HOUR);
47
- unit = "hour";
48
- } else if (absMs < WEEK) {
49
- value = Math.round(absMs / DAY);
50
- unit = "day";
51
- } else if (absMs < YEAR) {
52
- value = Math.round(absMs / WEEK);
53
- unit = "week";
54
- } else {
55
- value = Math.round(absMs / YEAR);
56
- unit = "year";
57
- }
58
-
59
- if (value !== 1) unit += "s";
60
-
61
- return isFuture
62
- ? `in ${value} ${unit}`
63
- : `${value} ${unit} ago`;
64
- }
@@ -1 +0,0 @@
1
- export * from "./initialiseForm";
@@ -1,14 +0,0 @@
1
- import type { ActionResponse } from "../API/apiRes";
2
-
3
- export function initialiseForm<T extends string = string>(
4
- ...fieldNames: T[]
5
- ): ActionResponse<T, T[]> {
6
- let fieldErrors = {} as Record<T, T[]>;
7
- for (const fieldName of fieldNames) {
8
- fieldErrors[fieldName] = [] as T[];
9
- }
10
- return {
11
- success: false,
12
- fieldErrors,
13
- };
14
- }
@@ -1 +0,0 @@
1
- export * from "./remap";
@@ -1,28 +0,0 @@
1
- /**
2
- * This function returns the rounded corresponding value in the target range using linear interpolation.
3
- *
4
- * @param value - The input value to remap.
5
- * @param from - The source range as a tuple [min, max].
6
- * @param to - The target range as a tuple [min, max].
7
- * @returns The remapped value in the target range.
8
- *
9
- * @example
10
- * remap(3, [1, 10], [1, 5]); // 2
11
- */
12
- export function remap(
13
- value: number,
14
- from: [number, number],
15
- to: [number, number],
16
- roundTo: "floor" | "ceil" = "floor",
17
- ): number {
18
- const [fromMin, fromMax] = from;
19
- const [toMin, toMax] = to;
20
-
21
- if (roundTo == "ceil")
22
- return Math.ceil(
23
- toMin + ((value - fromMin) * (toMax - toMin)) / (fromMax - fromMin),
24
- );
25
- return Math.floor(
26
- toMin + ((value - fromMin) * (toMax - toMin)) / (fromMax - fromMin),
27
- );
28
- }
@@ -1 +0,0 @@
1
- export * from "./mergeRefs";
@@ -1,16 +0,0 @@
1
- import type { RefObject, Ref } from "react";
2
-
3
- export function mergeRefs<T>(
4
- ...refs: (Ref<T> | undefined)[]
5
- ): (instance: T | null) => void {
6
- return (instance) => {
7
- for (const ref of refs) {
8
- if (!ref) continue;
9
- if (typeof ref === "function") {
10
- ref(instance);
11
- } else {
12
- (ref as RefObject<T | null>).current = instance;
13
- }
14
- }
15
- };
16
- }
@@ -1,3 +0,0 @@
1
- export * from "./cn";
2
- export * from "./stringCases";
3
- export * from "./cssSafeString";
package/src/Strings/cn.ts DELETED
@@ -1,3 +0,0 @@
1
- export function cn(...classNames: (string | undefined)[]) {
2
- return classNames.filter((c) => c).join(" ");
3
- }
@@ -1,13 +0,0 @@
1
- export function cssSafeString(str: string) {
2
- const encoded = encodeURIComponent(str)
3
- .toLowerCase()
4
- .replace(/\.|%[0-9a-z]{2}/gi, "");
5
-
6
- const firstChar = encoded.charAt(0);
7
- if (firstChar.match(/^[0-9-]/)) {
8
- // Check if the first character is a number or hyphen
9
- return "_" + encoded;
10
- }
11
-
12
- return encoded;
13
- }
@@ -1,50 +0,0 @@
1
- function toWords(str: string): string[] {
2
- return str
3
- .replace(/([a-z])([A-Z])/g, "$1 $2") // camelCase → camel Case
4
- .replace(/[_\-]+/g, " ") // snake_case, kebab-case → spaces
5
- .trim()
6
- .split(/\s+/)
7
- .filter(Boolean)
8
- .map((w) => w.toLowerCase());
9
- }
10
-
11
- export function toCapitalCase(str: string | undefined): string {
12
- if (!str) return "";
13
- const words = toWords(str);
14
- return words.map((w) => w[0].toUpperCase() + w.slice(1)).join("");
15
- }
16
-
17
- export function toCamelCase(str: string | undefined): string {
18
- if (!str) return "";
19
-
20
- const words = toWords(str);
21
- return words
22
- .map((w, i) => (i === 0 ? w : w[0].toUpperCase() + w.slice(1)))
23
- .join("");
24
- }
25
-
26
- export function toKebabCase(str: string | undefined): string {
27
- if (!str) return "";
28
-
29
- return toWords(str).join("-");
30
- }
31
-
32
- export function toSnakeCase(str: string | undefined): string {
33
- if (!str) return "";
34
-
35
- return toWords(str).join("_");
36
- }
37
-
38
- export function toConstantCase(str: string | undefined): string {
39
- if (!str) return "";
40
-
41
- return toWords(str).join("_").toUpperCase();
42
- }
43
-
44
- export function toCapitalised(str: string | undefined): string {
45
- if (!str) return "";
46
-
47
- const words = toWords(str);
48
- if (words.length === 0) return "";
49
- return words.map((w) => w[0].toUpperCase() + w.slice(1)).join(" ");
50
- }
package/src/index.ts DELETED
@@ -1,8 +0,0 @@
1
- export * from "./API/_index";
2
- export * from "./Calculations/_index";
3
- export * from "./Code/_index";
4
- export * from "./Dates/_index";
5
- export * from "./Forms/_index";
6
- export * from "./Numbers/_index";
7
- export * from "./React/_index";
8
- export * from "./Strings/_index";
package/tsconfig.json DELETED
@@ -1,32 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
4
- "target": "ES2022",
5
- "useDefineForClassFields": true,
6
- "lib": ["ES2022", "DOM", "DOM.Iterable"],
7
- "module": "ESNext",
8
- "resolveJsonModule": true,
9
- "allowJs": true,
10
- "skipLibCheck": true,
11
-
12
- /* Bundler mode */
13
- "moduleResolution": "bundler",
14
- "allowImportingTsExtensions": true,
15
- "verbatimModuleSyntax": true,
16
- "moduleDetection": "force",
17
- "noEmit": true,
18
- "jsx": "react-jsx",
19
- // Output
20
- "declaration": true,
21
- "outDir": "./dist",
22
-
23
- /* Linting */
24
- "strict": true,
25
- "noUnusedLocals": true,
26
- "noUnusedParameters": true,
27
- "erasableSyntaxOnly": true,
28
- "noFallthroughCasesInSwitch": true,
29
- "noUncheckedSideEffectImports": true
30
- },
31
- "include": ["src"]
32
- }