@usman404/crowjs 1.0.4 → 1.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.
@@ -17,38 +17,57 @@ export class TextField extends Input{
17
17
  * @param {number} options.borderWidth - Border width
18
18
  * @param {number} options.cornerRadius - Corner radius
19
19
  * @param {boolean} options.enableShadow - Enable shadow
20
- * @param {string} options.shadowColor - Shadow color
21
- * @param {number} options.shadowIntensity - Shadow opacity
22
- * @param {number} options.shadowSpread - Shadow spread
23
- * @param {number} options.shadowDetail - Shadow layers
20
+ * @param {string} options.shadowColor - Shadow color (CSS color string)
21
+ * @param {number} options.shadowBlur - Shadow blur radius
22
+ * @param {number} options.shadowOffsetX - Shadow offset on X axis
23
+ * @param {number} options.shadowOffsetY - Shadow offset on Y axis
24
24
  * @param {string} options.placeholder - Placeholder text
25
+ * @param {string} options.placeholderColor - Placeholder text color (muted)
25
26
  * @param {string} options.text - Initial text
26
27
  * @param {string} options.textAlign - Text alignment
27
- * @param {number} options.padding - Internal padding
28
+ * @param {number} options.pad - Internal padding
29
+ * @param {number} options.margin - General margin for all sides
30
+ * @param {number} options.marginx - Horizontal margin (left and right)
31
+ * @param {number} options.marginy - Vertical margin (top and bottom)
32
+ * @param {number} options.marginl - Left margin
33
+ * @param {number} options.marginr - Right margin
34
+ * @param {number} options.margint - Top margin
35
+ * @param {number} options.marginb - Bottom margin
28
36
  */
29
37
  constructor(x, y, width, height,
30
38
  {
31
39
  id=null,
32
40
  parent=null,
33
- backgroundColor='rgb(255, 255, 255)',
34
- textColor='rgb(0, 0, 0)',
41
+ backgroundColor='rgb(30, 30, 46)',
42
+ textColor='rgb(224, 224, 224)',
35
43
  borderFlag = true,
36
- borderColor = color(0),
44
+ borderColor = color('#3a3a4d'),
37
45
  borderWidth = 1,
38
- cornerRadius = 0,
46
+ cornerRadius = 8,
39
47
  enableShadow=false,
40
- shadowColor= 'rgb(0,0,0)',
41
- shadowIntensity= 0.4,
42
- shadowSpread= 3,
43
- shadowDetail=5,
48
+ shadowColor= 'rgba(0,0,0,0.5)',
49
+ shadowBlur= 12,
50
+ shadowOffsetX= 0,
51
+ shadowOffsetY= 4,
44
52
  placeholder="",
53
+ placeholderColor='rgb(120, 120, 140)',
45
54
  text="",
46
55
  textAlign = "left",
47
- padding = 10,
56
+ pad = 10,
57
+ margin = 0,
58
+ marginx = null,
59
+ marginy = null,
60
+ marginl = null,
61
+ marginr = null,
62
+ margint = null,
63
+ marginb = null,
64
+ minWidth = 0,
65
+ minHeight = 0,
66
+ showDebugOverlay = false,
48
67
  }={}) {
49
68
  super(x, y, width, height, backgroundColor, borderFlag, borderColor,
50
- borderWidth, cornerRadius, enableShadow, shadowColor, shadowIntensity,
51
- shadowSpread, shadowDetail, {parent: parent, type: "Input", id: id});
69
+ borderWidth, cornerRadius, enableShadow, shadowColor, shadowBlur,
70
+ shadowOffsetX, shadowOffsetY, {parent: parent, type: "Input", id: id, margin: margin, marginx: marginx, marginy: marginy, marginl: marginl, marginr: marginr, margint: margint, marginb: marginb, minWidth: minWidth, minHeight: minHeight, showDebugOverlay: showDebugOverlay});
52
71
 
53
72
  this.cursorPos = 0;
54
73
  this.text = text;
@@ -56,19 +75,32 @@ export class TextField extends Input{
56
75
  this.displayXOffset = 0;
57
76
 
58
77
  this.textAlign = textAlign;
59
- this.padding = padding;
78
+ this.pad = pad;
79
+ this.padl = pad;
80
+ this.padr = pad;
81
+ this.padt = pad;
82
+ this.padb = pad;
60
83
  this.textColor = textColor;
61
84
  this.placeholder = placeholder;
85
+ this.placeholderColor = placeholderColor;
62
86
 
63
87
  this.cursorVisible = true;
64
- this.lastCursorToggle = millis();
88
+ this.lastCursorToggle = 0;
65
89
  this.cursorBlinkInterval = 500;
66
90
 
67
91
  this.selectionStart = null;
68
92
  this.selectionEnd = null;
69
93
  this.isSelecting = false;
70
94
 
95
+ // Key repeat state for long-press behavior
96
+ this._lastKeyCode = null;
97
+ this._keyDownStartTime = 0;
98
+ this._lastRepeatTime = 0;
99
+ this._keyRepeatDelay = 500; // ms before repeat starts
100
+ this._keyRepeatInterval = 30; // ms between repeats
101
+
71
102
  this.addEventListener("keyPress", (event)=>this.onKeyPress(event));
103
+ this.addEventListener("keyDown", (event)=>this.onKeyDown(event));
72
104
  // this.addEventListener("click", (event)=>this.onMouseClick(event));
73
105
  this.addEventListener("press", (event)=>this.onMousePress(event)); // NEW
74
106
  this.addEventListener("drag", (event)=>this.onMouseDrag(event));
@@ -83,7 +115,8 @@ export class TextField extends Input{
83
115
  * @param {MouseEvent} event - The hover event
84
116
  */
85
117
  onMouseHover(event){
86
- // event.stopPropagation();
118
+ cursor('text');
119
+ event.stopPropagation();
87
120
  }
88
121
 
89
122
  /**
@@ -108,6 +141,9 @@ export class TextField extends Input{
108
141
  // Cursor usually hides after blur in textfields
109
142
  this.cursorVisible = false;
110
143
 
144
+ // Reset key repeat state
145
+ this._lastKeyCode = null;
146
+
111
147
  console.log("blur called!");
112
148
 
113
149
  event.stopPropagation();
@@ -161,38 +197,122 @@ export class TextField extends Input{
161
197
  }
162
198
  }
163
199
 
164
- onKeyPress(event) {
165
- // ADDED: if there's an active selection and user types or presses backspace/delete,
166
- // we should remove the selection first (so typed char replaces selection)
200
+ /**
201
+ * Handles continuous key down for key repeat (long-press arrow keys, backspace, etc.)
202
+ * Called from draw loop while key is held.
203
+ */
204
+ onKeyDown(event) {
205
+ const now = millis();
206
+ const repeatableKeys = [LEFT_ARROW, RIGHT_ARROW, BACKSPACE];
207
+
208
+ if (!repeatableKeys.includes(keyCode)) {
209
+ return;
210
+ }
211
+
212
+ // Detect new key press
213
+ if (this._lastKeyCode !== keyCode) {
214
+ this._lastKeyCode = keyCode;
215
+ this._keyDownStartTime = now;
216
+ this._lastRepeatTime = 0;
217
+ return; // first press handled by onKeyPress
218
+ }
219
+
220
+ // Wait for initial delay before repeating
221
+ if (now - this._keyDownStartTime < this._keyRepeatDelay) {
222
+ return;
223
+ }
224
+
225
+ // Throttle repeats
226
+ if (now - this._lastRepeatTime < this._keyRepeatInterval) {
227
+ return;
228
+ }
229
+
230
+ this._lastRepeatTime = now;
231
+ this._handleRepeatable();
232
+ }
233
+
234
+ /**
235
+ * Executes the repeatable key action (shared by onKeyPress and onKeyDown repeat).
236
+ */
237
+ _handleRepeatable() {
167
238
  const hasSelection = this.selectionStart !== null && this.selectionStart !== this.selectionEnd;
168
239
 
169
240
  if (keyCode === LEFT_ARROW) {
241
+ if (keyIsDown(SHIFT) && (this.selectionStart === null || this.selectionStart === this.selectionEnd)) {
242
+ this.selectionStart = this.cursorPos;
243
+ this.selectionEnd = this.cursorPos;
244
+ }
245
+
170
246
  if (keyIsDown(CONTROL)) {
171
247
  this.jumpLeftByOneWord();
172
248
  } else {
173
249
  this.moveCursorLeft(1);
174
250
  }
175
- // collapse selection when using arrows (typical behavior)
176
- this.selectionStart = this.selectionEnd = this.cursorPos;
251
+
252
+ if (keyIsDown(SHIFT)) {
253
+ this.selectionEnd = this.cursorPos;
254
+ } else {
255
+ this.selectionStart = this.selectionEnd = this.cursorPos;
256
+ }
177
257
  } else if (keyCode === RIGHT_ARROW) {
258
+ if (keyIsDown(SHIFT) && (this.selectionStart === null || this.selectionStart === this.selectionEnd)) {
259
+ this.selectionStart = this.cursorPos;
260
+ this.selectionEnd = this.cursorPos;
261
+ }
262
+
178
263
  if (keyIsDown(CONTROL)) {
179
264
  this.jumpRightByOneWord();
180
265
  } else {
181
266
  this.moveCursorRight(1);
182
267
  }
183
- this.selectionStart = this.selectionEnd = this.cursorPos;
268
+
269
+ if (keyIsDown(SHIFT)) {
270
+ this.selectionEnd = this.cursorPos;
271
+ } else {
272
+ this.selectionStart = this.selectionEnd = this.cursorPos;
273
+ }
184
274
  } else if (keyCode === BACKSPACE) {
185
275
  if (hasSelection) {
186
- // ADDED: delete selection (Backspace with selection deletes selection)
187
276
  this.deleteSelectedText();
188
277
  } else if (keyIsDown(CONTROL)) {
189
278
  this.deleteOneWord();
190
279
  } else {
191
280
  this.deleteOneChar();
192
281
  }
193
- // collapse selection after deletion
194
282
  this.selectionStart = this.selectionEnd = this.cursorPos;
283
+ }
284
+
285
+ this.cursorVisible = true;
286
+ this.lastCursorToggle = millis();
287
+ }
288
+
289
+ onKeyPress(event) {
290
+ // Reset key repeat tracking on new press
291
+ this._lastKeyCode = keyCode;
292
+ this._keyDownStartTime = millis();
293
+ this._lastRepeatTime = 0;
294
+
295
+ // ADDED: if there's an active selection and user types or presses backspace/delete,
296
+ // we should remove the selection first (so typed char replaces selection)
297
+ const hasSelection = this.selectionStart !== null && this.selectionStart !== this.selectionEnd;
298
+
299
+ if (keyCode === LEFT_ARROW || keyCode === RIGHT_ARROW || keyCode === BACKSPACE) {
300
+ this._handleRepeatable();
301
+ return; // cursor/selection already updated, skip to end
302
+ } else if (keyIsDown(CONTROL) && (key === 'a' || key === 'A')) {
303
+ // Select all text in the field
304
+ this.selectionStart = 0;
305
+ this.selectionEnd = this.text.length;
306
+ this.cursorPos = this.text.length;
307
+ // Prevent the browser from selecting all page content
308
+ if (event.nativeEvent) {
309
+ event.nativeEvent.preventDefault();
310
+ }
195
311
  } else if (key.length === 1) {
312
+ // Skip if Ctrl is held (avoid inserting control characters)
313
+ if (keyIsDown(CONTROL)) {
314
+ return;
315
+ }
196
316
  // ADDED: if a selection exists, remove it before insertion so typed char replaces selection
197
317
  if (hasSelection) {
198
318
  this.deleteSelectedText();
@@ -222,10 +342,10 @@ export class TextField extends Input{
222
342
 
223
343
  if (this.textAlign === "left") {
224
344
  // left aligned starts at left padding, minus the scroll offset
225
- return this.x + this.padding - this.displayXOffset;
345
+ return this.x + this.pad - this.displayXOffset;
226
346
  } else if (this.textAlign === "right") {
227
347
  // right aligned: text ends at width - padding, so start is that minus fullWidth
228
- return this.x + this.width - this.padding - fullWidth - this.displayXOffset;
348
+ return this.x + this.width - this.pad - fullWidth - this.displayXOffset;
229
349
  } else { // center or anything else
230
350
  return this.x + this.width / 2 - fullWidth / 2 - this.displayXOffset;
231
351
  }
@@ -278,6 +398,10 @@ export class TextField extends Input{
278
398
 
279
399
  // ADDED: clear selection anchors and ensure they reflect cursor
280
400
  this.selectionStart = this.selectionEnd = null;
401
+
402
+ // Reset scroll offset and ensure cursor is visible
403
+ this.displayXOffset = 0;
404
+ this.scrollCursorIntoView();
281
405
  }
282
406
  }
283
407
 
@@ -406,8 +530,8 @@ export class TextField extends Input{
406
530
  cursorX = this.findTextWidth(0, this.cursorPos);
407
531
  }
408
532
 
409
- if (cursorX - this.displayXOffset > this.width - this.padding) {
410
- this.displayXOffset = cursorX - this.width + 2*this.padding;
533
+ if (cursorX - this.displayXOffset > this.width - this.pad) {
534
+ this.displayXOffset = cursorX - this.width + 2*this.pad;
411
535
  }
412
536
 
413
537
  if(!cursorX){
@@ -425,8 +549,8 @@ export class TextField extends Input{
425
549
  cursorX = this.findTextWidth(0, this.cursorPos);
426
550
  }
427
551
 
428
- if(cursorX - this.displayXOffset < this.padding){
429
- this.displayXOffset = cursorX - this.padding;
552
+ if(cursorX - this.displayXOffset < this.pad){
553
+ this.displayXOffset = cursorX - this.pad;
430
554
  }
431
555
 
432
556
  if(!cursorX){
@@ -480,7 +604,7 @@ export class TextField extends Input{
480
604
 
481
605
  let x, y;
482
606
  if(this.textAlign==="left"){
483
- x = this.x - this.displayXOffset + this.padding;
607
+ x = this.x - this.displayXOffset + this.pad;
484
608
  } else if(this.textAlign==="right") {
485
609
  x = this.x - this.displayXOffset + this.width - this.textSize;
486
610
  }
@@ -492,16 +616,23 @@ export class TextField extends Input{
492
616
 
493
617
  let highlightX = x + this.findTextWidth(0, start);
494
618
  let highlightWidth = this.findTextWidth(start, end);
495
- let highlightY = y - this.textSize * 0.7;
496
619
 
497
620
  push();
498
621
  fill('rgba(15, 111, 206, 0.7)');
499
622
  noStroke();
500
- rect(highlightX, highlightY, highlightWidth, this.textSize);
623
+ rect(highlightX, this.y + this.pad, highlightWidth, this.height - 2 * this.pad);
501
624
  pop();
502
625
  }
503
626
 
504
- text(this.text, x, y);
627
+ if (this.text.length === 0 && this.placeholder && !this.isFocused) {
628
+ push();
629
+ fill(this.placeholderColor);
630
+ noStroke();
631
+ text(this.placeholder, x, y);
632
+ pop();
633
+ } else {
634
+ text(this.text, x, y);
635
+ }
505
636
 
506
637
  if (millis() - this.lastCursorToggle > this.cursorBlinkInterval) {
507
638
  this.cursorVisible = !this.cursorVisible;
@@ -513,7 +644,7 @@ export class TextField extends Input{
513
644
  if (this.cursorVisible) {
514
645
  stroke(this.textColor);
515
646
  strokeWeight(2);
516
- line(cursorX, y - this.textSize * 0.5, cursorX, y + this.textSize * 0.45);
647
+ line(cursorX, this.y + this.pad, cursorX, this.y + this.height - this.pad);
517
648
  }
518
649
  }
519
650
 
@@ -531,7 +662,7 @@ export class TextField extends Input{
531
662
  * Updates text size based on current height
532
663
  */
533
664
  updateTextSize(){
534
- this.textSize = this.height * 0.9;
665
+ this.textSize = (this.height - 2 * this.pad) * 0.9;
535
666
  this.scrollCursorIntoView();
536
667
  }
537
668
 
@@ -13,19 +13,26 @@ export class UIComponent extends Component{
13
13
  * @param {number} borderWidth - Border width
14
14
  * @param {number} cornerRadius - Corner radius for rounded corners
15
15
  * @param {boolean} enableShadow - Whether to render shadow
16
- * @param {string} shadowColor - Shadow color in RGB format
17
- * @param {number} shadowIntensity - Shadow opacity (0-1)
18
- * @param {number} shadowSpread - Shadow spread amount
19
- * @param {number} shadowDetail - Number of shadow layers
16
+ * @param {string} shadowColor - Shadow color (CSS color string)
17
+ * @param {number} shadowBlur - Shadow blur radius
18
+ * @param {number} shadowOffsetX - Shadow offset on X axis
19
+ * @param {number} shadowOffsetY - Shadow offset on Y axis
20
20
  * @param {Object} options - Additional options
21
21
  * @param {Component|null} options.parent - Parent component
22
22
  * @param {string} options.type - Component type
23
23
  * @param {string|null} options.id - Component ID
24
+ * @param {number} options.margin - General margin for all sides
25
+ * @param {number} options.marginx - Horizontal margin (left and right)
26
+ * @param {number} options.marginy - Vertical margin (top and bottom)
27
+ * @param {number} options.marginl - Left margin
28
+ * @param {number} options.marginr - Right margin
29
+ * @param {number} options.margint - Top margin
30
+ * @param {number} options.marginb - Bottom margin
24
31
  */
25
32
  constructor(x, y, width, height, backgroundColor, borderFlag, borderColor,
26
- borderWidth, cornerRadius, enableShadow, shadowColor, shadowIntensity,
27
- shadowSpread, shadowDetail, {parent=null, type="", id=null} = {}){
28
- super(x, y, width, height, {parent: parent, type: type, id: id});
33
+ borderWidth, cornerRadius, enableShadow, shadowColor, shadowBlur,
34
+ shadowOffsetX, shadowOffsetY, {parent=null, type="", id=null, margin=0, marginx=null, marginy=null, marginl=null, marginr=null, margint=null, marginb=null, minWidth=0, minHeight=0, showDebugOverlay=false} = {}){
35
+ super(x, y, width, height, {parent: parent, type: type, id: id, margin: margin, marginx: marginx, marginy: marginy, marginl: marginl, marginr: marginr, margint: margint, marginb: marginb, minWidth: minWidth, minHeight: minHeight, showDebugOverlay: showDebugOverlay});
29
36
 
30
37
  this.backgroundColor = backgroundColor;
31
38
 
@@ -38,10 +45,10 @@ export class UIComponent extends Component{
38
45
  this.enableShadow = enableShadow;
39
46
 
40
47
  if(this.enableShadow){
41
- this.shadowColor = shadowColor;//rgb value
42
- this.shadowIntensity = shadowIntensity;//opacity value between 0 and 1
43
- this.shadowSpread = shadowSpread;//stroke width of each of those rectangles
44
- this.shadowDetail = shadowDetail;//number of rectangles that will be drawn around the component
48
+ this.shadowColor = shadowColor;
49
+ this.shadowBlur = shadowBlur;
50
+ this.shadowOffsetX = shadowOffsetX;
51
+ this.shadowOffsetY = shadowOffsetY;
45
52
  }
46
53
 
47
54
  this.cornerRadius = cornerRadius;
@@ -57,41 +64,67 @@ export class UIComponent extends Component{
57
64
  */
58
65
  updateHeight(){};
59
66
  /**
60
- * Converts RGB color string to array of numbers
61
- * @param {string} shadowColor - RGB color string like "rgb(255,255,255)"
62
- * @returns {number[]|null} Array of [r, g, b] values or null if invalid
67
+ * Updates shadow color
68
+ * @param {string} shadowColor - Shadow color (CSS color string)
63
69
  */
64
- rgbToArray(shadowColor) {
65
- let match = shadowColor.match(/\d+/g);
66
- return match ? match.map(Number) : null;
70
+ setShadowColor(shadowColor){
71
+ this.shadowColor = shadowColor;
72
+ }
73
+
74
+ /**
75
+ * Updates shadow blur radius
76
+ * @param {number} shadowBlur - Shadow blur
77
+ */
78
+ setShadowBlur(shadowBlur){
79
+ this.shadowBlur = shadowBlur;
80
+ }
81
+
82
+ /**
83
+ * Updates shadow X offset
84
+ * @param {number} shadowOffsetX - Shadow offset on X axis
85
+ */
86
+ setShadowOffsetX(shadowOffsetX){
87
+ this.shadowOffsetX = shadowOffsetX;
88
+ }
89
+
90
+ /**
91
+ * Updates shadow Y offset
92
+ * @param {number} shadowOffsetY - Shadow offset on Y axis
93
+ */
94
+ setShadowOffsetY(shadowOffsetY){
95
+ this.shadowOffsetY = shadowOffsetY;
67
96
  }
68
97
  /**
69
98
  * Renders a shadow effect around the component
70
99
  * @param {Object} options - Shadow rendering options
71
100
  */
72
101
  drawShadow({}={}){
73
- let color = this.rgbToArray(this.shadowColor);
74
- if(color==null){
75
- console.log("shadow color value is not in the correct format: rgb(0,0,0)");
102
+ if(this.width<=0 || this.height<=0){
76
103
  return;
77
104
  }
78
105
 
79
- if(this.shadowIntensity>1){
80
- this.shadowIntensity=1;
81
- console.log("shadow intensity should be between 0 and 1 inclusive.\nAny value given outside of the range will be clipped to the ends.");
82
- } else if(this.shadowIntensity<0){
83
- console.log("shadow intensity should be between 0 and 1 inclusive.\nAny value given outside of the range will be clipped to the ends.");
84
- this.shadowIntensity=0;
85
- }
106
+ let blur = Math.max(0, this.shadowBlur ?? 0);
107
+ let offsetX = this.shadowOffsetX ?? 0;
108
+ let offsetY = this.shadowOffsetY ?? 0;
109
+ let resolvedShadowColor = (typeof this.shadowColor === "string")
110
+ ? this.shadowColor
111
+ : (this.shadowColor && this.shadowColor.toString ? this.shadowColor.toString() : "rgba(0,0,0,0.35)");
86
112
 
87
- for(let i=1; i<=this.shadowDetail; i++){
88
- push();
89
- noFill();
90
- let alpha = this.shadowIntensity * pow(1 - i / this.shadowDetail, 2);
91
- stroke(`rgba(${color[0]}, ${color[1]}, ${color[2]}, ${alpha})`);
92
- strokeWeight(this.shadowSpread);
93
- rect(this.x-((i*this.shadowSpread)/2), this.y-((i*this.shadowSpread)/2), this.width+(i*this.shadowSpread), this.height+(i*this.shadowSpread), this.cornerRadius);
94
- pop();
113
+ if(blur===0 && offsetX===0 && offsetY===0){
114
+ return;
95
115
  }
116
+
117
+ let baseFill = this.backgroundColor ?? color(0, 0, 0, 0);
118
+
119
+ push();
120
+ let ctx = drawingContext;
121
+ ctx.shadowColor = resolvedShadowColor;
122
+ ctx.shadowBlur = blur;
123
+ ctx.shadowOffsetX = offsetX;
124
+ ctx.shadowOffsetY = offsetY;
125
+ noStroke();
126
+ fill(baseFill);
127
+ rect(this.x, this.y, this.width, this.height, this.cornerRadius);
128
+ pop();
96
129
  }
97
130
  }
package/index.js CHANGED
@@ -15,8 +15,10 @@ const ScrollFrame = require('./Frames/ScrollFrame');
15
15
  // UIComponents
16
16
  const Input = require('./UIComponents/Input');
17
17
  const Label = require('./UIComponents/Label');
18
+ const Button = require('./UIComponents/Button');
18
19
  const TextField = require('./UIComponents/TextField');
19
20
  const UIComponent = require('./UIComponents/UIComponent');
21
+ const Icon = require('./UIComponents/Icon');
20
22
 
21
23
  module.exports = {
22
24
  // Core
@@ -36,6 +38,8 @@ module.exports = {
36
38
  // UIComponents
37
39
  Input,
38
40
  Label,
41
+ Button,
39
42
  TextField,
40
- UIComponent
43
+ UIComponent,
44
+ Icon
41
45
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@usman404/crowjs",
3
- "version": "1.0.4",
3
+ "version": "1.1.0",
4
4
  "description": "lightweight and extensible GUI library built on top of p5.js",
5
5
  "keywords": [
6
6
  "CROWJS"
@@ -18,6 +18,13 @@
18
18
  "type": "commonjs",
19
19
  "main": "index.js",
20
20
  "scripts": {
21
- "test": "echo \"Error: no test specified\" && exit 1"
21
+ "test": "echo \"Error: no test specified\" && exit 1",
22
+ "docs:dev": "vitepress dev docs --host 0.0.0.0",
23
+ "docs:build": "vitepress build docs",
24
+ "docs:preview": "vitepress preview docs"
25
+ },
26
+ "devDependencies": {
27
+ "@iconify/vue": "^4.1.2",
28
+ "vitepress": "^1.6.4"
22
29
  }
23
30
  }
package/problems.txt ADDED
@@ -0,0 +1 @@
1
+ - work on default look of all items.