Skip to content

Commit 1ebd4db

Browse files
authored
Add string utils and cleanup deps (#79)
* fix: Change `culori` dep to devDependencies (only used for generation and not runtime) * fix: Change `tailwindcss` dep to devDependecies * Replace `isFunction` util with simple `typeof foo === 'function'` * feat: Add string utils `toCamelCase()`, `toSnakeCase()`, `toKebabCase()`, and `toPascalCase()`
1 parent c4a6509 commit 1ebd4db

File tree

13 files changed

+260
-59
lines changed

13 files changed

+260
-59
lines changed

.changeset/dull-meals-post.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@layerstack/utils': patch
3+
---
4+
5+
feat: Add string utils `toCamelCase()`, `toSnakeCase()`, `toKebabCase()`, and `toPascalCase()`

.changeset/four-signs-smell.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@layerstack/tailwind': patch
3+
---
4+
5+
fix: Change `culori` dep to devDependencies (only used for generation and not runtime)

.changeset/lazy-bottles-rule.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@layerstack/tailwind': patch
3+
---
4+
5+
fix: Change `tailwindcss` dep to devDependecies

packages/svelte-stores/src/lib/localStore.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import { writable } from 'svelte/store';
2-
import { isFunction } from 'lodash-es';
32

43
import { parse, stringify } from '@layerstack/utils';
54
import { browser } from '@layerstack/utils/env';
@@ -42,10 +41,11 @@ function localStore<Value>(key: string, initialValue: Value, options?: LocalStor
4241
? expireObject(previousExpiry, previousExpiry)
4342
: previousExpiry;
4443

45-
const expiry = isFunction(options?.expiry)
46-
? // @ts-expect-error
47-
options?.expiry(prunedPreviousExpiry) // Update expiry on write
48-
: options?.expiry;
44+
const expiry =
45+
typeof options?.expiry === 'function'
46+
? // @ts-expect-error
47+
options?.expiry(prunedPreviousExpiry) // Update expiry on write
48+
: options?.expiry;
4949
previousExpiry = expiry;
5050

5151
localStorage.setItem(key, stringify({ value: val, expiry }));

packages/svelte-table/src/lib/stores.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import { writable } from 'svelte/store';
22
import type { ComponentEvents } from 'svelte';
3-
import { isFunction } from 'lodash-es';
43
import { index } from 'd3-array';
54

65
import { sortFunc } from '@layerstack/utils';
@@ -78,7 +77,7 @@ function createState(column: ColumnDef, props?: TableOrderProps, prevState?: Tab
7877
: 'asc';
7978

8079
let handler: SortFunc | undefined = undefined;
81-
if (isFunction(column.orderBy)) {
80+
if (typeof column.orderBy === 'function') {
8281
handler = column.orderBy;
8382
} else if (typeof column.orderBy === 'string') {
8483
handler = sortFunc(column.orderBy, direction);

packages/svelte-table/src/lib/utils.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { isFunction, get } from 'lodash-es';
1+
import { get } from 'lodash-es';
22

33
import { PeriodType, parseDate } from '@layerstack/utils';
44

@@ -99,7 +99,7 @@ export function getCellHeader(column: ColumnDef) {
9999

100100
export function getCellValue(column: ColumnDef, rowData: any, rowIndex?: number) {
101101
let value = undefined;
102-
if (isFunction(column.value)) {
102+
if (typeof column.value === 'function') {
103103
value = column.value?.(rowData, rowIndex);
104104
}
105105

@@ -109,7 +109,7 @@ export function getCellValue(column: ColumnDef, rowData: any, rowIndex?: number)
109109

110110
if (
111111
typeof value === 'string' &&
112-
!isFunction(column.format) &&
112+
typeof column.format !== 'function' &&
113113
(column.format ?? 'none') in PeriodType
114114
) {
115115
// Convert date string to Date instance

packages/tailwind/package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,12 @@
2525
"@types/d3-array": "^3.2.1",
2626
"@types/lodash-es": "^4.17.12",
2727
"daisyui": "^4.12.24",
28+
"culori": "^4.0.1",
2829
"prettier": "^3.5.3",
2930
"rimraf": "6.0.1",
3031
"tslib": "^2.8.1",
3132
"tsx": "^4.19.4",
33+
"tailwindcss": "^4.1.5",
3234
"typescript": "^5.8.3",
3335
"vite": "^6.3.5",
3436
"vitest": "^3.1.3"
@@ -37,11 +39,9 @@
3739
"dependencies": {
3840
"@layerstack/utils": "workspace:^",
3941
"clsx": "^2.1.1",
40-
"culori": "^4.0.1",
4142
"d3-array": "^3.2.4",
4243
"lodash-es": "^4.17.21",
43-
"tailwind-merge": "^3.2.0",
44-
"tailwindcss": "^4.1.5"
44+
"tailwind-merge": "^3.2.0"
4545
},
4646
"main": "./dist/index.js",
4747
"exports": {

packages/utils/src/lib/object.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
import { get, camelCase, mergeWith } from 'lodash-es';
1+
import { get, mergeWith } from 'lodash-es';
22
import { entries, fromEntries, keys } from './typeHelpers.js';
3+
import { toCamelCase } from './string.js';
34

45
export function isLiteralObject(obj: any): obj is object {
56
return obj && typeof obj === 'object' && obj.constructor === Object;
@@ -11,7 +12,7 @@ export function isEmptyObject(obj: any) {
1112

1213
export function camelCaseKeys(obj: any) {
1314
return keys(obj).reduce(
14-
(acc, key) => ((acc[camelCase(key ? String(key) : undefined)] = obj[key]), acc),
15+
(acc, key) => ((acc[toCamelCase(key ? String(key) : '')] = obj[key]), acc),
1516
{} as any
1617
);
1718
}

packages/utils/src/lib/rollup.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { rollup } from 'd3-array';
2-
import { get, isFunction } from 'lodash-es';
2+
import { get } from 'lodash-es';
33

44
export default function <T = any>(
55
data: T[],
@@ -13,7 +13,7 @@ export default function <T = any>(
1313
// }
1414

1515
const keyFuncs = keys.map((key) => {
16-
if (isFunction(key)) {
16+
if (typeof key === 'function') {
1717
return key;
1818
} else if (typeof key === 'string') {
1919
return (d: any) => get(d, key) || emptyKey;
Lines changed: 141 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,149 @@
1-
import { describe, it, expect } from 'vitest';
1+
import { describe, test, expect } from 'vitest';
22

3-
import { toTitleCase } from './string.js';
3+
import {
4+
isUpperCase,
5+
romanize,
6+
toCamelCase,
7+
toKebabCase,
8+
toPascalCase,
9+
toSnakeCase,
10+
toTitleCase,
11+
truncate,
12+
} from './string.js';
13+
14+
describe('isUpperCase()', () => {
15+
test.each([
16+
['A', true],
17+
['a', false],
18+
['THE QUICK BROWN FOX', true],
19+
['the quick brown fox', false],
20+
['The Quick Brown Fox', false],
21+
['The quick brown fox', false],
22+
])('isUpperCase(%s) => %s', (original, expected) => {
23+
expect(isUpperCase(original)).equal(expected);
24+
});
25+
});
426

527
describe('toTitleCase()', () => {
6-
it('basic', () => {
7-
const original = 'this is a test';
8-
const expected = 'This is a Test';
28+
test.each([
29+
['A long time ago', 'A Long Time Ago'], // sentence
30+
['the quick brown fox', 'The Quick Brown Fox'], // lower case
31+
['THE QUICK BROWN FOX', 'The Quick Brown Fox'], // upper case
32+
['the_quick_brown_fox', 'The Quick Brown Fox'], // snake case
33+
['the-quick-brown-fox', 'The Quick Brown Fox'], // kebab case
34+
['theQuickBrownFox', 'The Quick Brown Fox'], // pascal case
35+
['the - quick * brown# fox', 'The Quick Brown Fox'], // punctuation
36+
])('toTitleCase(%s) => %s', (original, expected) => {
937
expect(toTitleCase(original)).equal(expected);
1038
});
39+
});
1140

12-
it('basic', () => {
13-
const original = 'A long time ago';
14-
const expected = 'A Long Time Ago';
15-
expect(toTitleCase(original)).equal(expected);
41+
describe('toCamelCase()', () => {
42+
test.each([
43+
['the quick brown fox', 'theQuickBrownFox'], // lower case
44+
['the_quick_brown_fox', 'theQuickBrownFox'], // snake case
45+
['the-quick-brown-fox', 'theQuickBrownFox'], // kebab case
46+
['THE-QUICK-BROWN-FOX', 'theQuickBrownFox'], // snake case (all caps)
47+
['theQuickBrownFox', 'theQuickBrownFox'], // pascal case
48+
['thequickbrownfox', 'thequickbrownfox'], // lowercase
49+
['the - quick * brown# fox', 'theQuickBrownFox'], // punctuation
50+
['behold theQuickBrownFox', 'beholdTheQuickBrownFox'],
51+
['Behold theQuickBrownFox', 'beholdTheQuickBrownFox'],
52+
['The quick brown FOX', 'theQuickBrownFox'], // all caps words are camel-cased
53+
['theQUickBrownFox', 'theQUickBrownFox'], // all caps substrings >= 4 chars are camel-cased
54+
['theQUIckBrownFox', 'theQUIckBrownFox'],
55+
])('toCamelCase(%s) => %s', (original, expected) => {
56+
expect(toCamelCase(original)).equal(expected);
57+
});
58+
});
59+
60+
describe('toSnakeCase()', () => {
61+
test.each([
62+
['the quick brown fox', 'the_quick_brown_fox'], // lower case
63+
['the-quick-brown-fox', 'the_quick_brown_fox'], // kebab case
64+
['the_quick_brown_fox', 'the_quick_brown_fox'], // snake case
65+
['theQuickBrownFox', 'the_quick_brown_fox'], // pascal case
66+
['theQuickBrown Fox', 'the_quick_brown_fox'], // space separated words
67+
['thequickbrownfox', 'thequickbrownfox'], // no spaces
68+
['the - quick * brown# fox', 'the_quick_brown_fox'], // punctuation
69+
['theQUICKBrownFox', 'the_q_u_i_c_k_brown_fox'], // all caps words are snake-cased
70+
])('toSnakeCase(%s) => %s', (original, expected) => {
71+
expect(toSnakeCase(original)).equal(expected);
72+
});
73+
});
74+
75+
describe('toKebabCase()', () => {
76+
test.each([
77+
['the quick brown fox', 'the-quick-brown-fox'], // lower case
78+
['the-quick-brown-fox', 'the-quick-brown-fox'], // kebab case
79+
['the_quick_brown_fox', 'the-quick-brown-fox'], // snake case
80+
['theQuickBrownFox', 'the-quick-brown-fox'], // pascal case
81+
['theQuickBrown Fox', 'the-quick-brown-fox'], // space separated words
82+
['thequickbrownfox', 'thequickbrownfox'], // no spaces
83+
['the - quick * brown# fox', 'the-quick-brown-fox'], // punctuation
84+
['theQUICKBrownFox', 'the-q-u-i-c-k-brown-fox'], // all caps words are snake-cased
85+
])('toKebabCase(%s) => %s', (original, expected) => {
86+
expect(toKebabCase(original)).equal(expected);
87+
});
88+
});
89+
90+
describe('toPascalCase()', () => {
91+
test.each([
92+
['the quick brown fox', 'TheQuickBrownFox'], // lower case
93+
['the_quick_brown_fox', 'TheQuickBrownFox'], // snake case
94+
['the-quick-brown-fox', 'TheQuickBrownFox'], // kebab case
95+
['theQuickBrownFox', 'TheQuickBrownFox'], // pascal case
96+
['thequickbrownfox', 'Thequickbrownfox'], // lowercase
97+
['the - quick * brown# fox', 'TheQuickBrownFox'], // punctuation
98+
['theQUICKBrownFox', 'TheQUICKBrownFox'], // all caps words are pascal-cased
99+
])('toPascalCase(%s) => %s', (original, expected) => {
100+
expect(toPascalCase(original)).equal(expected);
101+
});
102+
});
103+
104+
describe('romanize()', () => {
105+
test.each([
106+
[1, 'I'],
107+
[2, 'II'],
108+
[3, 'III'],
109+
[4, 'IV'],
110+
[5, 'V'],
111+
[6, 'VI'],
112+
[7, 'VII'],
113+
[8, 'VIII'],
114+
[9, 'IX'],
115+
[10, 'X'],
116+
[11, 'XI'],
117+
[12, 'XII'],
118+
[13, 'XIII'],
119+
[14, 'XIV'],
120+
[15, 'XV'],
121+
[16, 'XVI'],
122+
[17, 'XVII'],
123+
[18, 'XVIII'],
124+
[19, 'XIX'],
125+
[20, 'XX'],
126+
[40, 'XL'],
127+
[49, 'XLIX'],
128+
[50, 'L'],
129+
[90, 'XC'],
130+
[100, 'C'],
131+
[400, 'CD'],
132+
[500, 'D'],
133+
[900, 'CM'],
134+
[1000, 'M'],
135+
])('romanize(%s) => %s', (original, expected) => {
136+
expect(romanize(original)).equal(expected);
137+
});
138+
});
139+
140+
describe('truncate()', () => {
141+
test.each([
142+
['the quick brown fox', 9, undefined, 'the quick…'],
143+
['the quick brown fox', 15, undefined, 'the quick brown…'],
144+
['the quick brown fox', 15, 3, 'the quick br…fox'],
145+
['the quick brown fox', 9, Infinity, '…brown fox'],
146+
])('truncate(%s, %s) => %s', (original, totalChars, endChars, expected) => {
147+
expect(truncate(original, totalChars, endChars)).equal(expected);
16148
});
17149
});

0 commit comments

Comments
 (0)