@dava96/osrs-icons 1.0.15 → 1.0.16

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
@@ -75,6 +75,85 @@ You can use the package directly in the browser via ESM.sh:
75
75
  </script>
76
76
  ```
77
77
 
78
+ ### Cursor Packs
79
+
80
+ Pre-built thematic icon groups — each pack groups related icons by their in-game state:
81
+
82
+ ```ts
83
+ import { sharkPack, dragonDaggerPack, bucketPack } from '@dava96/osrs-icons';
84
+
85
+ // Fish: raw → cooked → burnt
86
+ element.style.cursor = sharkPack.cooked;
87
+
88
+ // Dragon dagger: base → poisoned → p+ → p++
89
+ element.style.cursor = dragonDaggerPack.poisonedPlusPlus;
90
+
91
+ // Bucket fill progression — great for loading states
92
+ const stages = bucketPack.stages; // [empty, 1/5, 2/5, 3/5, 4/5, full]
93
+ const index = Math.min(Math.floor(progress / 100 * stages.length), stages.length - 1);
94
+ element.style.cursor = stages[index];
95
+ ```
96
+
97
+ **Available packs:**
98
+
99
+ | Pack | Keys |
100
+ |------|------|
101
+ | `sharkPack` | `raw`, `cooked`, `burnt` |
102
+ | `herringPack` | `raw`, `cooked`, `burnt`, `error` |
103
+ | `anglerfishPack` | `raw`, `cooked`, `burnt` |
104
+ | `dragonDaggerPack` | `base`, `poisoned`, `poisonedPlus`, `poisonedPlusPlus` |
105
+ | `goldPack` | `ore`, `bar` |
106
+ | `ironPack` | `ore`, `bar` |
107
+ | `coinsPack` | `_1` to `_10000` + `stages[]` |
108
+ | `bucketPack` | `empty` to `full` + `stages[]` |
109
+
110
+ ### Flip Cursor
111
+
112
+ Many OSRS icons face right, but cursors typically point left. Flip any icon horizontally at runtime:
113
+
114
+ ```ts
115
+ import { abyssalWhip, flipCursor } from '@dava96/osrs-icons';
116
+
117
+ const leftFacing = await flipCursor(abyssalWhip);
118
+ document.body.style.cursor = leftFacing;
119
+ ```
120
+
121
+ Results are cached internally — flipping the same icon twice returns instantly. Browser-only (uses Canvas API); returns the original value in Node.js/SSR.
122
+
123
+ ### Apply Cursors
124
+
125
+ Map OSRS icons to standard CSS cursor states with a one-liner:
126
+
127
+ ```ts
128
+ import { abyssalWhip, dragonScimitar, bucketPack, applyCursors } from '@dava96/osrs-icons';
129
+
130
+ const cleanup = applyCursors({
131
+ default: abyssalWhip,
132
+ pointer: dragonScimitar,
133
+ wait: bucketPack.full,
134
+ });
135
+
136
+ // Later, revert to browser defaults
137
+ cleanup();
138
+ ```
139
+
140
+ You can also scope cursors to a specific element:
141
+
142
+ ```ts
143
+ applyCursors({ default: abyssalWhip }, document.getElementById('game-area')!);
144
+ ```
145
+
146
+ ### Error Cursor 🐟
147
+
148
+ Use the iconic red herring as your error cursor:
149
+
150
+ ```ts
151
+ import { errorCursor } from '@dava96/osrs-icons';
152
+
153
+ // Also available via herringPack.error
154
+ element.style.cursor = errorCursor;
155
+ ```
156
+
78
157
  ## How It Works
79
158
 
80
159
  The build script fetches every inventory sprite (~17,400 icons) from the
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Standard CSS cursor states that can be mapped to OSRS icons.
3
+ *
4
+ * @see https://developer.mozilla.org/en-US/docs/Web/CSS/cursor
5
+ */
6
+ export type CursorState = 'default' | 'pointer' | 'wait' | 'text' | 'move' | 'crosshair' | 'grab' | 'grabbing' | 'not-allowed' | 'help' | 'progress' | 'cell' | 'copy' | 'alias' | 'no-drop' | 'col-resize' | 'row-resize' | 'n-resize' | 'e-resize' | 's-resize' | 'w-resize' | 'zoom-in' | 'zoom-out';
7
+ /**
8
+ * A mapping of CSS cursor states to OSRS icon cursor strings.
9
+ *
10
+ * Only include the states you want to override — unlisted states
11
+ * keep their default browser cursor.
12
+ */
13
+ export type CursorMapping = Partial<Record<CursorState, string>>;
14
+ /**
15
+ * Applies OSRS cursor icons to CSS cursor states on a target element
16
+ * or globally across the entire page.
17
+ *
18
+ * Injects a `<style>` tag with CSS rules that map each specified cursor
19
+ * state to the given OSRS icon. Returns a cleanup function that removes
20
+ * the injected styles and restores the previous cursors.
21
+ *
22
+ * **Browser-only** — in non-browser environments this is a no-op that
23
+ * returns an empty cleanup function.
24
+ *
25
+ * @param mapping - An object mapping CSS cursor states to OSRS cursor strings.
26
+ * @param target - Optional element to scope the cursors to. Defaults to the
27
+ * entire document (global).
28
+ * @returns A cleanup function that reverts all applied cursors.
29
+ *
30
+ * @example
31
+ * ```ts
32
+ * import { abyssalWhip, dragonScimitar, applyCursors } from '@dava96/osrs-icons';
33
+ *
34
+ * // Apply globally
35
+ * const remove = applyCursors({
36
+ * default: abyssalWhip,
37
+ * pointer: dragonScimitar,
38
+ * });
39
+ *
40
+ * // Later, revert to browser defaults
41
+ * remove();
42
+ * ```
43
+ *
44
+ * @example
45
+ * ```ts
46
+ * import { bucketPack, applyCursors } from '@dava96/osrs-icons';
47
+ *
48
+ * // Scope to a specific element
49
+ * const cleanup = applyCursors(
50
+ * { wait: bucketPack.full },
51
+ * document.getElementById('loading-area')!,
52
+ * );
53
+ * ```
54
+ *
55
+ * @example
56
+ * ```ts
57
+ * import { herringPack, applyCursors } from '@dava96/osrs-icons';
58
+ *
59
+ * // Use the red herring for error states!
60
+ * applyCursors({ not-allowed: herringPack.error });
61
+ * ```
62
+ */
63
+ export declare function applyCursors(mapping: CursorMapping, target?: HTMLElement): () => void;
@@ -0,0 +1,82 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.applyCursors = applyCursors;
4
+ /**
5
+ * Applies OSRS cursor icons to CSS cursor states on a target element
6
+ * or globally across the entire page.
7
+ *
8
+ * Injects a `<style>` tag with CSS rules that map each specified cursor
9
+ * state to the given OSRS icon. Returns a cleanup function that removes
10
+ * the injected styles and restores the previous cursors.
11
+ *
12
+ * **Browser-only** — in non-browser environments this is a no-op that
13
+ * returns an empty cleanup function.
14
+ *
15
+ * @param mapping - An object mapping CSS cursor states to OSRS cursor strings.
16
+ * @param target - Optional element to scope the cursors to. Defaults to the
17
+ * entire document (global).
18
+ * @returns A cleanup function that reverts all applied cursors.
19
+ *
20
+ * @example
21
+ * ```ts
22
+ * import { abyssalWhip, dragonScimitar, applyCursors } from '@dava96/osrs-icons';
23
+ *
24
+ * // Apply globally
25
+ * const remove = applyCursors({
26
+ * default: abyssalWhip,
27
+ * pointer: dragonScimitar,
28
+ * });
29
+ *
30
+ * // Later, revert to browser defaults
31
+ * remove();
32
+ * ```
33
+ *
34
+ * @example
35
+ * ```ts
36
+ * import { bucketPack, applyCursors } from '@dava96/osrs-icons';
37
+ *
38
+ * // Scope to a specific element
39
+ * const cleanup = applyCursors(
40
+ * { wait: bucketPack.full },
41
+ * document.getElementById('loading-area')!,
42
+ * );
43
+ * ```
44
+ *
45
+ * @example
46
+ * ```ts
47
+ * import { herringPack, applyCursors } from '@dava96/osrs-icons';
48
+ *
49
+ * // Use the red herring for error states!
50
+ * applyCursors({ not-allowed: herringPack.error });
51
+ * ```
52
+ */
53
+ function applyCursors(mapping, target) {
54
+ if (typeof document === 'undefined') {
55
+ return () => { };
56
+ }
57
+ const styleElement = document.createElement('style');
58
+ const selector = target ? `[data-osrs-cursor-id="${generateId()}"]` : '*';
59
+ if (target) {
60
+ target.setAttribute('data-osrs-cursor-id', selector.slice(22, -2));
61
+ }
62
+ const rules = Object.entries(mapping)
63
+ .map(([state, cursorValue]) => {
64
+ const fallback = state === 'default' ? 'auto' : state;
65
+ return `${selector} { cursor: ${cursorValue.replace(', auto', `, ${fallback}`)}; }`;
66
+ })
67
+ .join('\n');
68
+ styleElement.textContent = rules;
69
+ styleElement.setAttribute('data-osrs-cursors', 'true');
70
+ document.head.appendChild(styleElement);
71
+ return () => {
72
+ styleElement.remove();
73
+ if (target) {
74
+ target.removeAttribute('data-osrs-cursor-id');
75
+ }
76
+ };
77
+ }
78
+ /** Generates a short unique ID for scoping cursor styles to elements. */
79
+ let idCounter = 0;
80
+ function generateId() {
81
+ return `osrs-${++idCounter}-${Date.now().toString(36)}`;
82
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,41 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const node_assert_1 = __importDefault(require("node:assert"));
7
+ const applyCursors_1 = require("./applyCursors");
8
+ // ── applyCursors tests (Node.js — no DOM) ──────────────────────────
9
+ function testApplyCursorsIsExported() {
10
+ node_assert_1.default.strictEqual(typeof applyCursors_1.applyCursors, 'function');
11
+ console.log('✓ applyCursors: is a function export');
12
+ }
13
+ function testApplyCursorsReturnsCleanupInNode() {
14
+ const cleanup = (0, applyCursors_1.applyCursors)({ default: 'test-cursor' });
15
+ node_assert_1.default.strictEqual(typeof cleanup, 'function', 'should return a cleanup function');
16
+ cleanup();
17
+ console.log('✓ applyCursors: returns no-op cleanup function in Node.js');
18
+ }
19
+ function testCursorStateTypeIsExported() {
20
+ const validStates = [
21
+ 'default', 'pointer', 'wait', 'text', 'move',
22
+ 'crosshair', 'grab', 'grabbing', 'not-allowed', 'help',
23
+ ];
24
+ node_assert_1.default.ok(validStates.length > 0, 'CursorState type should compile');
25
+ console.log('✓ CursorState: type is exported and usable');
26
+ }
27
+ function testCursorMappingTypeAcceptsPartialStates() {
28
+ const mapping = {
29
+ default: 'cursor-a',
30
+ pointer: 'cursor-b',
31
+ };
32
+ node_assert_1.default.strictEqual(Object.keys(mapping).length, 2);
33
+ console.log('✓ CursorMapping: accepts partial state mappings');
34
+ }
35
+ // ── Runner ─────────────────────────────────────────────────────────
36
+ console.log('\nRunning applyCursors tests...\n');
37
+ testApplyCursorsIsExported();
38
+ testApplyCursorsReturnsCleanupInNode();
39
+ testCursorStateTypeIsExported();
40
+ testCursorMappingTypeAcceptsPartialStates();
41
+ console.log('\nAll applyCursors tests passed!\n');
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Horizontally flips a CSS cursor icon so it points in the opposite direction.
3
+ *
4
+ * Many OSRS inventory icons face right, but cursor conventions typically
5
+ * expect left-facing images. This helper mirrors the embedded base64 PNG
6
+ * using the Canvas API and returns a new, ready-to-use CSS cursor string.
7
+ *
8
+ * Results are cached internally — flipping the same icon twice returns
9
+ * the cached value instantly.
10
+ *
11
+ * **Browser-only** — requires the Canvas API. In non-browser environments
12
+ * (Node.js, SSR) the original cursor string is returned unchanged.
13
+ *
14
+ * @param cursorValue - A CSS cursor string (e.g. `url('data:image/png;base64,...'), auto`).
15
+ * @returns A promise that resolves to a new cursor string with the image flipped.
16
+ *
17
+ * @example
18
+ * ```ts
19
+ * import { abyssalWhip, flipCursor } from '@dava96/osrs-icons';
20
+ *
21
+ * const leftFacingWhip = await flipCursor(abyssalWhip);
22
+ * document.body.style.cursor = leftFacingWhip;
23
+ * ```
24
+ *
25
+ * @example
26
+ * ```ts
27
+ * import { dragonDaggerPack, flipCursor } from '@dava96/osrs-icons';
28
+ *
29
+ * // Flip an entire pack
30
+ * const flippedDagger = await flipCursor(dragonDaggerPack.base);
31
+ * ```
32
+ */
33
+ export declare function flipCursor(cursorValue: string): Promise<string>;
@@ -0,0 +1,79 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.flipCursor = flipCursor;
4
+ /**
5
+ * Horizontally flips a CSS cursor icon so it points in the opposite direction.
6
+ *
7
+ * Many OSRS inventory icons face right, but cursor conventions typically
8
+ * expect left-facing images. This helper mirrors the embedded base64 PNG
9
+ * using the Canvas API and returns a new, ready-to-use CSS cursor string.
10
+ *
11
+ * Results are cached internally — flipping the same icon twice returns
12
+ * the cached value instantly.
13
+ *
14
+ * **Browser-only** — requires the Canvas API. In non-browser environments
15
+ * (Node.js, SSR) the original cursor string is returned unchanged.
16
+ *
17
+ * @param cursorValue - A CSS cursor string (e.g. `url('data:image/png;base64,...'), auto`).
18
+ * @returns A promise that resolves to a new cursor string with the image flipped.
19
+ *
20
+ * @example
21
+ * ```ts
22
+ * import { abyssalWhip, flipCursor } from '@dava96/osrs-icons';
23
+ *
24
+ * const leftFacingWhip = await flipCursor(abyssalWhip);
25
+ * document.body.style.cursor = leftFacingWhip;
26
+ * ```
27
+ *
28
+ * @example
29
+ * ```ts
30
+ * import { dragonDaggerPack, flipCursor } from '@dava96/osrs-icons';
31
+ *
32
+ * // Flip an entire pack
33
+ * const flippedDagger = await flipCursor(dragonDaggerPack.base);
34
+ * ```
35
+ */
36
+ async function flipCursor(cursorValue) {
37
+ if (flipCache.has(cursorValue)) {
38
+ return flipCache.get(cursorValue);
39
+ }
40
+ if (typeof document === 'undefined') {
41
+ return cursorValue;
42
+ }
43
+ const dataUrlMatch = cursorValue.match(/url\('(.*?)'\)/);
44
+ if (!dataUrlMatch) {
45
+ return cursorValue;
46
+ }
47
+ const originalDataUrl = dataUrlMatch[1];
48
+ const flippedDataUrl = await mirrorImage(originalDataUrl);
49
+ const flippedCursor = cursorValue.replace(originalDataUrl, flippedDataUrl);
50
+ flipCache.set(cursorValue, flippedCursor);
51
+ return flippedCursor;
52
+ }
53
+ /** Internal cache to avoid re-processing the same icon. */
54
+ const flipCache = new Map();
55
+ /**
56
+ * Loads a data URL into an off-screen canvas, mirrors it horizontally,
57
+ * and returns the flipped image as a new data URL.
58
+ */
59
+ function mirrorImage(dataUrl) {
60
+ return new Promise((resolve, reject) => {
61
+ const img = new Image();
62
+ img.onload = () => {
63
+ const canvas = document.createElement('canvas');
64
+ canvas.width = img.width;
65
+ canvas.height = img.height;
66
+ const ctx = canvas.getContext('2d');
67
+ if (!ctx) {
68
+ resolve(dataUrl);
69
+ return;
70
+ }
71
+ ctx.translate(canvas.width, 0);
72
+ ctx.scale(-1, 1);
73
+ ctx.drawImage(img, 0, 0);
74
+ resolve(canvas.toDataURL('image/png'));
75
+ };
76
+ img.onerror = () => reject(new Error('Failed to load cursor image for flipping'));
77
+ img.src = dataUrl;
78
+ });
79
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,39 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const node_assert_1 = __importDefault(require("node:assert"));
7
+ const flip_1 = require("./flip");
8
+ // ── flipCursor tests (Node.js — no Canvas API) ────────────────────
9
+ function testFlipCursorIsExported() {
10
+ node_assert_1.default.strictEqual(typeof flip_1.flipCursor, 'function');
11
+ console.log('✓ flipCursor: is a function export');
12
+ }
13
+ async function testFlipCursorFallsBackInNode() {
14
+ const input = "url('data:image/png;base64,abc123'), auto";
15
+ const result = await (0, flip_1.flipCursor)(input);
16
+ node_assert_1.default.strictEqual(result, input, 'should return original in non-browser environment');
17
+ console.log('✓ flipCursor: returns original value in Node.js (no Canvas)');
18
+ }
19
+ async function testFlipCursorHandlesPlainString() {
20
+ const input = 'not-a-cursor-value';
21
+ const result = await (0, flip_1.flipCursor)(input);
22
+ node_assert_1.default.strictEqual(result, input, 'should return as-is when no url() pattern');
23
+ console.log('✓ flipCursor: returns plain strings unchanged');
24
+ }
25
+ async function testFlipCursorReturnsSamePromiseType() {
26
+ const input = "url('data:image/png;base64,test'), auto";
27
+ const result = (0, flip_1.flipCursor)(input);
28
+ node_assert_1.default.ok(result instanceof Promise, 'should return a Promise');
29
+ console.log('✓ flipCursor: returns a Promise');
30
+ }
31
+ // ── Runner ─────────────────────────────────────────────────────────
32
+ console.log('\nRunning flip tests...\n');
33
+ testFlipCursorIsExported();
34
+ (async () => {
35
+ await testFlipCursorFallsBackInNode();
36
+ await testFlipCursorHandlesPlainString();
37
+ await testFlipCursorReturnsSamePromiseType();
38
+ console.log('\nAll flip tests passed!\n');
39
+ })();
@@ -1,5 +1,9 @@
1
1
  export * from './generated/icons';
2
2
  export * from './generated/meta';
3
+ export * from './packs';
4
+ export * from './flip';
5
+ export * from './applyCursors';
6
+ export { redHerring as errorCursor } from './generated/icons';
3
7
  /**
4
8
  * Extracts the raw `data:image/png;base64,...` URL from one or more
5
9
  * CSS cursor values.
package/dist/cjs/index.js CHANGED
@@ -14,9 +14,15 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
14
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
+ exports.errorCursor = void 0;
17
18
  exports.toDataUrl = toDataUrl;
18
19
  __exportStar(require("./generated/icons"), exports);
19
20
  __exportStar(require("./generated/meta"), exports);
21
+ __exportStar(require("./packs"), exports);
22
+ __exportStar(require("./flip"), exports);
23
+ __exportStar(require("./applyCursors"), exports);
24
+ var icons_1 = require("./generated/icons");
25
+ Object.defineProperty(exports, "errorCursor", { enumerable: true, get: function () { return icons_1.redHerring; } });
20
26
  function toDataUrl(input) {
21
27
  if (typeof input === 'string') {
22
28
  return extractUrl(input);