@leftium/gg 0.0.38 → 0.0.40

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
@@ -159,6 +159,49 @@ gg(fg('rgb(255,99,71)')`Tomato text`);
159
159
  - Node.js terminal
160
160
  - All environments that support ANSI escape codes
161
161
 
162
+ ## Text Styling (ANSI)
163
+
164
+ Add visual emphasis to logs with `bold()`, `italic()`, `underline()`, and `dim()`. These can be used standalone or chained with colors:
165
+
166
+ ```javascript
167
+ import { gg, fg, bg, bold, italic, underline, dim } from '@leftium/gg';
168
+
169
+ // Standalone text styles
170
+ gg(bold()`Bold text`);
171
+ gg(italic()`Italic text`);
172
+ gg(underline()`Underlined text`);
173
+ gg(dim()`Dimmed/faint text`);
174
+
175
+ // Combined with colors
176
+ gg(fg('red').bold()`Bold red error`);
177
+ gg(fg('green').bold()`Bold green success`);
178
+ gg(bg('yellow').italic()`Italic on yellow background`);
179
+ gg(fg('blue').underline()`Blue underlined text`);
180
+
181
+ // Multiple styles chained
182
+ gg(bold().italic()`Bold and italic`);
183
+ gg(fg('red').bold().underline()`Bold underlined red`);
184
+
185
+ // Reusable style presets
186
+ const finalStyle = fg('green').bold();
187
+ const interimStyle = fg('gray');
188
+
189
+ gg(finalStyle`final` + ' seg=0 "my name is John Kim Murphy" 96%');
190
+ gg(interimStyle`interim` + ' seg=0 "my name is John Kim Murphy" 90%');
191
+
192
+ // Mixed inline styling
193
+ gg(bold()`Important:` + ' normal text ' + italic()`with emphasis`);
194
+ ```
195
+
196
+ **Available styles:**
197
+
198
+ - `bold()` - Bold/strong text (font-weight: bold)
199
+ - `italic()` - Italic/emphasized text (font-style: italic)
200
+ - `underline()` - Underlined text (text-decoration: underline)
201
+ - `dim()` - Dimmed/faint text (opacity: 0.6)
202
+
203
+ Text styles work in the same environments as colors (browser console, GgConsole, terminal).
204
+
162
205
  ## Other Frameworks
163
206
 
164
207
  `gg()` works in any JavaScript project. The Vite plugins work with any Vite-based framework (React, Vue, Solid, etc.).
@@ -205,6 +205,41 @@ export function createGgPlugin(options, gg) {
205
205
  }
206
206
  });
207
207
  }
208
+ function toggleNamespaces(namespaces, enable) {
209
+ const currentPattern = filterPattern || 'gg:*';
210
+ let parts = currentPattern
211
+ .split(',')
212
+ .map((p) => p.trim())
213
+ .filter(Boolean);
214
+ namespaces.forEach((namespace) => {
215
+ const ns = namespace.trim();
216
+ if (enable) {
217
+ // Remove any exclusion for this namespace
218
+ parts = parts.filter((p) => p !== `-${ns}`);
219
+ }
220
+ else {
221
+ // Add exclusion if not already present
222
+ const exclusion = `-${ns}`;
223
+ if (!parts.includes(exclusion)) {
224
+ parts.push(exclusion);
225
+ }
226
+ }
227
+ });
228
+ filterPattern = parts.join(',');
229
+ // Simplify pattern
230
+ filterPattern = simplifyPattern(filterPattern);
231
+ // Sync enabledNamespaces from the NEW pattern
232
+ const allNamespaces = getAllCapturedNamespaces();
233
+ enabledNamespaces.clear();
234
+ const effectivePattern = filterPattern || 'gg:*';
235
+ allNamespaces.forEach((ns) => {
236
+ if (namespaceMatchesPattern(ns, effectivePattern)) {
237
+ enabledNamespaces.add(ns);
238
+ }
239
+ });
240
+ // Persist the new pattern
241
+ localStorage.setItem(FILTER_KEY, filterPattern);
242
+ }
208
243
  function soloNamespace(namespace) {
209
244
  const ns = namespace.trim();
210
245
  // Toggle: if already soloed on this namespace, restore all
@@ -377,6 +412,20 @@ export function createGgPlugin(options, gg) {
377
412
  .gg-log-ns[data-file]:hover::after {
378
413
  opacity: 1;
379
414
  }
415
+ /* Clickable namespace segments */
416
+ .gg-ns-segment {
417
+ cursor: pointer;
418
+ padding: 1px 2px;
419
+ border-radius: 2px;
420
+ transition: background 0.1s;
421
+ }
422
+ /* Only show segment hover styling when in filter mode */
423
+ .gg-log-grid.filter-mode .gg-ns-segment:hover {
424
+ background: rgba(0,0,0,0.1);
425
+ text-decoration: underline;
426
+ text-decoration-style: solid;
427
+ text-underline-offset: 2px;
428
+ }
380
429
  .gg-details {
381
430
  grid-column: 1 / -1;
382
431
  border-top: none;
@@ -870,6 +919,19 @@ export function createGgPlugin(options, gg) {
870
919
  renderLogs();
871
920
  return;
872
921
  }
922
+ // Handle "other" checkbox
923
+ if (target.classList.contains('gg-other-checkbox')) {
924
+ const otherNamespacesJson = target.getAttribute('data-other-namespaces');
925
+ if (!otherNamespacesJson)
926
+ return;
927
+ const otherNamespaces = JSON.parse(otherNamespacesJson);
928
+ // Toggle all "other" namespaces at once
929
+ toggleNamespaces(otherNamespaces, target.checked);
930
+ // localStorage already saved in toggleNamespaces()
931
+ renderFilterUI();
932
+ renderLogs();
933
+ return;
934
+ }
873
935
  // Handle individual namespace checkboxes
874
936
  if (target.classList.contains('gg-ns-checkbox')) {
875
937
  const namespace = target.getAttribute('data-namespace');
@@ -908,24 +970,50 @@ export function createGgPlugin(options, gg) {
908
970
  let checkboxesHTML = '';
909
971
  if (simple && allNamespaces.length > 0) {
910
972
  const allChecked = enabledCount === totalCount;
973
+ // Count frequency of each namespace in the buffer
974
+ const allEntries = buffer.getEntries();
975
+ const nsCounts = new Map();
976
+ allEntries.forEach((entry) => {
977
+ nsCounts.set(entry.namespace, (nsCounts.get(entry.namespace) || 0) + 1);
978
+ });
979
+ // Sort ALL namespaces by frequency (most common first)
980
+ const sortedAllNamespaces = [...allNamespaces].sort((a, b) => (nsCounts.get(b) || 0) - (nsCounts.get(a) || 0));
981
+ // Take top 5 most common (regardless of enabled state)
982
+ const displayedNamespaces = sortedAllNamespaces.slice(0, 5);
983
+ // Calculate "other" namespaces (not in top 5)
984
+ const displayedSet = new Set(displayedNamespaces);
985
+ const otherNamespaces = allNamespaces.filter((ns) => !displayedSet.has(ns));
986
+ const otherEnabledCount = otherNamespaces.filter((ns) => enabledNamespaces.has(ns)).length;
987
+ const otherTotalCount = otherNamespaces.length;
988
+ const otherChecked = otherEnabledCount > 0;
989
+ const otherCount = otherNamespaces.reduce((sum, ns) => sum + (nsCounts.get(ns) || 0), 0);
911
990
  checkboxesHTML = `
912
991
  <div class="gg-filter-checkboxes">
913
992
  <label class="gg-filter-checkbox" style="font-weight: bold;">
914
993
  <input type="checkbox" class="gg-all-checkbox" ${allChecked ? 'checked' : ''}>
915
994
  <span>ALL</span>
916
995
  </label>
917
- ${allNamespaces
996
+ ${displayedNamespaces
918
997
  .map((ns) => {
919
998
  // Check if namespace matches the current pattern
920
999
  const checked = namespaceMatchesPattern(ns, effectivePattern);
1000
+ const count = nsCounts.get(ns) || 0;
921
1001
  return `
922
1002
  <label class="gg-filter-checkbox">
923
1003
  <input type="checkbox" class="gg-ns-checkbox" data-namespace="${escapeHtml(ns)}" ${checked ? 'checked' : ''}>
924
- <span>${escapeHtml(ns)}</span>
1004
+ <span>${escapeHtml(ns)} (${count})</span>
925
1005
  </label>
926
1006
  `;
927
1007
  })
928
1008
  .join('')}
1009
+ ${otherTotalCount > 0
1010
+ ? `
1011
+ <label class="gg-filter-checkbox" style="opacity: 0.7;">
1012
+ <input type="checkbox" class="gg-other-checkbox" ${otherChecked ? 'checked' : ''} data-other-namespaces='${JSON.stringify(otherNamespaces)}'>
1013
+ <span>other (${otherCount})</span>
1014
+ </label>
1015
+ `
1016
+ : ''}
929
1017
  </div>
930
1018
  `;
931
1019
  }
@@ -1282,6 +1370,38 @@ export function createGgPlugin(options, gg) {
1282
1370
  }
1283
1371
  return;
1284
1372
  }
1373
+ // Handle clicking namespace segments
1374
+ if (target?.classList?.contains('gg-ns-segment')) {
1375
+ // When filter is collapsed, open in editor instead of filtering
1376
+ if (!filterExpanded) {
1377
+ const nsContainer = target.closest('.gg-log-ns');
1378
+ if (nsContainer?.hasAttribute('data-file')) {
1379
+ handleNamespaceClick(nsContainer);
1380
+ }
1381
+ return;
1382
+ }
1383
+ // When filter is expanded, apply hierarchical filtering
1384
+ const filter = target.getAttribute('data-filter');
1385
+ if (!filter)
1386
+ return;
1387
+ // Toggle behavior: if already at this filter, restore all
1388
+ if (filterPattern === filter) {
1389
+ filterPattern = 'gg:*';
1390
+ enabledNamespaces.clear();
1391
+ getAllCapturedNamespaces().forEach((ns) => enabledNamespaces.add(ns));
1392
+ }
1393
+ else {
1394
+ filterPattern = filter;
1395
+ enabledNamespaces.clear();
1396
+ getAllCapturedNamespaces()
1397
+ .filter((ns) => namespaceMatchesPattern(ns, filter))
1398
+ .forEach((ns) => enabledNamespaces.add(ns));
1399
+ }
1400
+ localStorage.setItem(FILTER_KEY, filterPattern);
1401
+ renderFilterUI();
1402
+ renderLogs();
1403
+ return;
1404
+ }
1285
1405
  // Handle clicking namespace to open in editor (when filter collapsed)
1286
1406
  if (target?.classList?.contains('gg-log-ns') &&
1287
1407
  target.hasAttribute('data-file') &&
@@ -1462,11 +1582,45 @@ export function createGgPlugin(options, gg) {
1462
1582
  logContainer.html('<div style="padding: 20px; text-align: center; opacity: 0.5;">No logs captured yet. Call gg() to see output here.</div>');
1463
1583
  return;
1464
1584
  }
1465
- const logsHTML = `<div class="gg-log-grid" style="grid-template-columns: ${gridColumns()};">${entries
1585
+ const logsHTML = `<div class="gg-log-grid${filterExpanded ? ' filter-mode' : ''}" style="grid-template-columns: ${gridColumns()};">${entries
1466
1586
  .map((entry, index) => {
1467
1587
  const color = entry.color || '#0066cc';
1468
1588
  const diff = `+${humanize(entry.diff)}`;
1469
1589
  const ns = escapeHtml(entry.namespace);
1590
+ // Split namespace into clickable segments on multiple delimiters: : @ / - _
1591
+ const parts = entry.namespace.split(/([:\/@\-_])/);
1592
+ const nsSegments = [];
1593
+ const delimiters = [];
1594
+ for (let i = 0; i < parts.length; i++) {
1595
+ if (i % 2 === 0) {
1596
+ // Even indices are segments
1597
+ if (parts[i])
1598
+ nsSegments.push(parts[i]);
1599
+ }
1600
+ else {
1601
+ // Odd indices are delimiters
1602
+ delimiters.push(parts[i]);
1603
+ }
1604
+ }
1605
+ let nsHTML = '';
1606
+ for (let i = 0; i < nsSegments.length; i++) {
1607
+ const segment = escapeHtml(nsSegments[i]);
1608
+ // Build filter pattern: reconstruct namespace up to this point
1609
+ let filterPattern = '';
1610
+ for (let j = 0; j <= i; j++) {
1611
+ filterPattern += nsSegments[j];
1612
+ if (j < i) {
1613
+ filterPattern += delimiters[j];
1614
+ }
1615
+ else if (j < nsSegments.length - 1) {
1616
+ filterPattern += delimiters[j] + '*';
1617
+ }
1618
+ }
1619
+ nsHTML += `<span class="gg-ns-segment" data-filter="${escapeHtml(filterPattern)}">${segment}</span>`;
1620
+ if (i < nsSegments.length - 1) {
1621
+ nsHTML += escapeHtml(delimiters[i]);
1622
+ }
1623
+ }
1470
1624
  // Format each arg individually - objects are expandable
1471
1625
  let argsHTML = '';
1472
1626
  let detailsHTML = '';
@@ -1569,7 +1723,7 @@ export function createGgPlugin(options, gg) {
1569
1723
  `<div class="gg-log-header">` +
1570
1724
  iconsCol +
1571
1725
  `<div class="gg-log-diff${soloClass}" style="color: ${color};"${soloAttr}>${diff}</div>` +
1572
- `<div class="gg-log-ns${soloClass}" style="color: ${color};"${soloAttr}${fileAttr}${lineAttr}${colAttr}${fileTitle}>${ns}</div>` +
1726
+ `<div class="gg-log-ns${soloClass}" style="color: ${color};" data-namespace="${escapeHtml(entry.namespace)}"${fileAttr}${lineAttr}${colAttr}${fileTitle}>${nsHTML}</div>` +
1573
1727
  `<div class="gg-log-handle"></div>` +
1574
1728
  `</div>` +
1575
1729
  `<div class="gg-log-content"${!entry.level && entry.src?.trim() && !/^['"`]/.test(entry.src) ? ` data-src="${escapeHtml(entry.src)}"` : ''}>${argsHTML}${stackHTML}</div>` +
@@ -1663,6 +1817,7 @@ export function createGgPlugin(options, gg) {
1663
1817
  * Supports:
1664
1818
  * - Basic 3/4-bit colors: \x1b[31m (fg red), \x1b[41m (bg red), \x1b[91m (bright fg), etc.
1665
1819
  * - 24-bit RGB: \x1b[38;2;r;g;bm (foreground), \x1b[48;2;r;g;bm (background)
1820
+ * - Text styles: \x1b[1m (bold), \x1b[2m (dim), \x1b[3m (italic), \x1b[4m (underline)
1666
1821
  * - Reset: \x1b[0m
1667
1822
  */
1668
1823
  function parseAnsiToHtml(text) {
@@ -1673,20 +1828,57 @@ export function createGgPlugin(options, gg) {
1673
1828
  let lastIndex = 0;
1674
1829
  let currentFg = null;
1675
1830
  let currentBg = null;
1831
+ let currentBold = false;
1832
+ let currentDim = false;
1833
+ let currentItalic = false;
1834
+ let currentUnderline = false;
1676
1835
  let match;
1677
1836
  while ((match = ansiRegex.exec(text)) !== null) {
1678
1837
  // Add text before this code (with current styling)
1679
1838
  const textBefore = text.slice(lastIndex, match.index);
1680
1839
  if (textBefore) {
1681
- html += wrapWithStyle(escapeHtml(textBefore), currentFg, currentBg);
1840
+ html += wrapWithStyle(escapeHtml(textBefore), currentFg, currentBg, currentBold, currentDim, currentItalic, currentUnderline);
1682
1841
  }
1683
1842
  // Parse the ANSI code
1684
1843
  const code = match[1];
1685
1844
  const parts = code.split(';').map(Number);
1686
1845
  if (parts[0] === 0) {
1687
- // Reset
1846
+ // Reset all
1688
1847
  currentFg = null;
1689
1848
  currentBg = null;
1849
+ currentBold = false;
1850
+ currentDim = false;
1851
+ currentItalic = false;
1852
+ currentUnderline = false;
1853
+ }
1854
+ else if (parts[0] === 1) {
1855
+ // Bold
1856
+ currentBold = true;
1857
+ }
1858
+ else if (parts[0] === 2) {
1859
+ // Dim/Faint
1860
+ currentDim = true;
1861
+ }
1862
+ else if (parts[0] === 3) {
1863
+ // Italic
1864
+ currentItalic = true;
1865
+ }
1866
+ else if (parts[0] === 4) {
1867
+ // Underline
1868
+ currentUnderline = true;
1869
+ }
1870
+ else if (parts[0] === 22) {
1871
+ // Normal intensity (not bold, not dim)
1872
+ currentBold = false;
1873
+ currentDim = false;
1874
+ }
1875
+ else if (parts[0] === 23) {
1876
+ // Not italic
1877
+ currentItalic = false;
1878
+ }
1879
+ else if (parts[0] === 24) {
1880
+ // Not underlined
1881
+ currentUnderline = false;
1690
1882
  }
1691
1883
  else if (parts[0] === 38 && parts[1] === 2 && parts.length >= 5) {
1692
1884
  // Foreground RGB: 38;2;r;g;b
@@ -1720,21 +1912,29 @@ export function createGgPlugin(options, gg) {
1720
1912
  // Add remaining text
1721
1913
  const remaining = text.slice(lastIndex);
1722
1914
  if (remaining) {
1723
- html += wrapWithStyle(escapeHtml(remaining), currentFg, currentBg);
1915
+ html += wrapWithStyle(escapeHtml(remaining), currentFg, currentBg, currentBold, currentDim, currentItalic, currentUnderline);
1724
1916
  }
1725
1917
  return html || escapeHtml(text);
1726
1918
  }
1727
1919
  /**
1728
- * Wrap text with inline color styles
1920
+ * Wrap text with inline color and text style CSS
1729
1921
  */
1730
- function wrapWithStyle(text, fg, bg) {
1731
- if (!fg && !bg)
1922
+ function wrapWithStyle(text, fg, bg, bold, dim, italic, underline) {
1923
+ if (!fg && !bg && !bold && !dim && !italic && !underline)
1732
1924
  return text;
1733
1925
  const styles = [];
1734
1926
  if (fg)
1735
1927
  styles.push(`color: ${fg}`);
1736
1928
  if (bg)
1737
1929
  styles.push(`background-color: ${bg}`);
1930
+ if (bold)
1931
+ styles.push('font-weight: bold');
1932
+ if (dim)
1933
+ styles.push('opacity: 0.6');
1934
+ if (italic)
1935
+ styles.push('font-style: italic');
1936
+ if (underline)
1937
+ styles.push('text-decoration: underline');
1738
1938
  return `<span style="${styles.join('; ')}">${text}</span>`;
1739
1939
  }
1740
1940
  return plugin;
@@ -42,6 +42,9 @@ export default function ggCallSitesPlugin(options = {}) {
42
42
  // This prevents rewriting library code (including gg itself when published)
43
43
  if (id.includes('/node_modules/'))
44
44
  return null;
45
+ // Don't transform the gg.ts file itself (contains gg function definitions)
46
+ if (id.includes('/gg.ts') || id.includes('/gg.js'))
47
+ return null;
45
48
  // Quick bail: no gg calls in this file
46
49
  if (!code.includes('gg(') &&
47
50
  !code.includes('gg.ns(') &&
package/dist/gg.d.ts CHANGED
@@ -70,6 +70,10 @@ type ColorTagFunction = (strings: TemplateStringsArray, ...values: unknown[]) =>
70
70
  interface ChainableColorFn extends ColorTagFunction {
71
71
  fg: (color: string) => ChainableColorFn;
72
72
  bg: (color: string) => ChainableColorFn;
73
+ bold: () => ChainableColorFn;
74
+ italic: () => ChainableColorFn;
75
+ underline: () => ChainableColorFn;
76
+ dim: () => ChainableColorFn;
73
77
  }
74
78
  /**
75
79
  * Foreground (text) color helper
@@ -91,6 +95,43 @@ export declare function fg(color: string): ChainableColorFn;
91
95
  * gg(bg('green').fg('white')`Success!`);
92
96
  */
93
97
  export declare function bg(color: string): ChainableColorFn;
98
+ /**
99
+ * Bold text style
100
+ * Can be used directly or chained with colors
101
+ *
102
+ * @example
103
+ * gg(bold()`Important text`);
104
+ * gg(bold().fg('red')`Bold red error`);
105
+ * gg(fg('green').bold()`Bold green success`);
106
+ */
107
+ export declare function bold(): ChainableColorFn;
108
+ /**
109
+ * Italic text style
110
+ * Can be used directly or chained with colors
111
+ *
112
+ * @example
113
+ * gg(italic()`Emphasized text`);
114
+ * gg(italic().fg('blue')`Italic blue`);
115
+ */
116
+ export declare function italic(): ChainableColorFn;
117
+ /**
118
+ * Underline text style
119
+ * Can be used directly or chained with colors
120
+ *
121
+ * @example
122
+ * gg(underline()`Underlined text`);
123
+ * gg(underline().fg('cyan')`Underlined cyan`);
124
+ */
125
+ export declare function underline(): ChainableColorFn;
126
+ /**
127
+ * Dim text style (faint/dimmed appearance)
128
+ * Can be used directly or chained with colors
129
+ *
130
+ * @example
131
+ * gg(dim()`Less important text`);
132
+ * gg(dim().fg('white')`Dimmed white`);
133
+ */
134
+ export declare function dim(): ChainableColorFn;
94
135
  export declare namespace gg {
95
136
  let _onLog: OnLogCallback | null;
96
137
  let ns: (nsLabel: string, ...args: unknown[]) => unknown;
package/dist/gg.js CHANGED
@@ -806,24 +806,45 @@ function parseColor(color) {
806
806
  }
807
807
  return null;
808
808
  }
809
+ /**
810
+ * ANSI style codes for text formatting
811
+ */
812
+ const STYLE_CODES = {
813
+ bold: '\x1b[1m',
814
+ dim: '\x1b[2m',
815
+ italic: '\x1b[3m',
816
+ underline: '\x1b[4m'
817
+ };
809
818
  /**
810
819
  * Internal helper to create chainable color function with method chaining
811
820
  */
812
- function createColorFunction(fgCode = '', bgCode = '') {
821
+ function createColorFunction(fgCode = '', bgCode = '', styleCode = '') {
813
822
  const tagFn = function (strings, ...values) {
814
823
  const text = strings.reduce((acc, str, i) => acc + str + (values[i] !== undefined ? String(values[i]) : ''), '');
815
- return fgCode + bgCode + text + '\x1b[0m';
824
+ return fgCode + bgCode + styleCode + text + '\x1b[0m';
816
825
  };
817
826
  // Add method chaining
818
827
  tagFn.fg = (color) => {
819
828
  const rgb = parseColor(color);
820
829
  const newFgCode = rgb ? `\x1b[38;2;${rgb.r};${rgb.g};${rgb.b}m` : '';
821
- return createColorFunction(newFgCode, bgCode);
830
+ return createColorFunction(newFgCode, bgCode, styleCode);
822
831
  };
823
832
  tagFn.bg = (color) => {
824
833
  const rgb = parseColor(color);
825
834
  const newBgCode = rgb ? `\x1b[48;2;${rgb.r};${rgb.g};${rgb.b}m` : '';
826
- return createColorFunction(fgCode, newBgCode);
835
+ return createColorFunction(fgCode, newBgCode, styleCode);
836
+ };
837
+ tagFn.bold = () => {
838
+ return createColorFunction(fgCode, bgCode, styleCode + STYLE_CODES.bold);
839
+ };
840
+ tagFn.italic = () => {
841
+ return createColorFunction(fgCode, bgCode, styleCode + STYLE_CODES.italic);
842
+ };
843
+ tagFn.underline = () => {
844
+ return createColorFunction(fgCode, bgCode, styleCode + STYLE_CODES.underline);
845
+ };
846
+ tagFn.dim = () => {
847
+ return createColorFunction(fgCode, bgCode, styleCode + STYLE_CODES.dim);
827
848
  };
828
849
  return tagFn;
829
850
  }
@@ -855,6 +876,51 @@ export function bg(color) {
855
876
  const bgCode = rgb ? `\x1b[48;2;${rgb.r};${rgb.g};${rgb.b}m` : '';
856
877
  return createColorFunction('', bgCode);
857
878
  }
879
+ /**
880
+ * Bold text style
881
+ * Can be used directly or chained with colors
882
+ *
883
+ * @example
884
+ * gg(bold()`Important text`);
885
+ * gg(bold().fg('red')`Bold red error`);
886
+ * gg(fg('green').bold()`Bold green success`);
887
+ */
888
+ export function bold() {
889
+ return createColorFunction('', '', STYLE_CODES.bold);
890
+ }
891
+ /**
892
+ * Italic text style
893
+ * Can be used directly or chained with colors
894
+ *
895
+ * @example
896
+ * gg(italic()`Emphasized text`);
897
+ * gg(italic().fg('blue')`Italic blue`);
898
+ */
899
+ export function italic() {
900
+ return createColorFunction('', '', STYLE_CODES.italic);
901
+ }
902
+ /**
903
+ * Underline text style
904
+ * Can be used directly or chained with colors
905
+ *
906
+ * @example
907
+ * gg(underline()`Underlined text`);
908
+ * gg(underline().fg('cyan')`Underlined cyan`);
909
+ */
910
+ export function underline() {
911
+ return createColorFunction('', '', STYLE_CODES.underline);
912
+ }
913
+ /**
914
+ * Dim text style (faint/dimmed appearance)
915
+ * Can be used directly or chained with colors
916
+ *
917
+ * @example
918
+ * gg(dim()`Less important text`);
919
+ * gg(dim().fg('white')`Dimmed white`);
920
+ */
921
+ export function dim() {
922
+ return createColorFunction('', '', STYLE_CODES.dim);
923
+ }
858
924
  /**
859
925
  * Hook for capturing gg() output (used by Eruda plugin)
860
926
  * Set this to a callback function to receive log entries
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { gg, fg, bg } from './gg.js';
1
+ import { gg, fg, bg, bold, italic, underline, dim } from './gg.js';
2
2
  import openInEditorPlugin from './open-in-editor.js';
3
3
  import ggCallSitesPlugin from './gg-call-sites-plugin.js';
4
4
  export { default as GgConsole } from './GgConsole.svelte';
5
- export { gg, fg, bg, openInEditorPlugin, ggCallSitesPlugin };
5
+ export { gg, fg, bg, bold, italic, underline, dim, openInEditorPlugin, ggCallSitesPlugin };
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  // Reexport your entry components here
2
- import { gg, fg, bg } from './gg.js';
2
+ import { gg, fg, bg, bold, italic, underline, dim } from './gg.js';
3
3
  import openInEditorPlugin from './open-in-editor.js';
4
4
  import ggCallSitesPlugin from './gg-call-sites-plugin.js';
5
5
  export { default as GgConsole } from './GgConsole.svelte';
6
- export { gg, fg, bg, openInEditorPlugin, ggCallSitesPlugin };
6
+ export { gg, fg, bg, bold, italic, underline, dim, openInEditorPlugin, ggCallSitesPlugin };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@leftium/gg",
3
- "version": "0.0.38",
3
+ "version": "0.0.40",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/Leftium/gg.git"