pict 1.0.366 → 1.0.368
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/dist/pict.js +774 -588
- package/dist/pict.js.map +1 -1
- package/dist/pict.min.js +2 -2
- package/dist/pict.min.js.map +1 -1
- package/package.json +4 -4
- package/source/Pict.js +30 -0
- package/source/providers/Pict-Icons-Base.js +107 -0
- package/source/providers/Provider-Icon.js +323 -0
- package/source/templates/Pict-Template-Icon.js +72 -0
- package/dist/pict.compatible.js +0 -8451
- package/dist/pict.compatible.js.map +0 -1
- package/dist/pict.compatible.min.js +0 -12
- package/dist/pict.compatible.min.js.map +0 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pict",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.368",
|
|
4
4
|
"description": "Pict browser library.",
|
|
5
5
|
"main": "source/Pict.js",
|
|
6
6
|
"scripts": {
|
|
@@ -56,7 +56,7 @@
|
|
|
56
56
|
"@types/sinon": "^17.0.4",
|
|
57
57
|
"eslint": "^9.39.1",
|
|
58
58
|
"globals": "^16.5.0",
|
|
59
|
-
"pict-docuserve": "^0.
|
|
59
|
+
"pict-docuserve": "^1.0.0",
|
|
60
60
|
"quackage": "^1.2.3",
|
|
61
61
|
"sinon": "^20.0.0",
|
|
62
62
|
"typescript": "^5.9.3"
|
|
@@ -64,8 +64,8 @@
|
|
|
64
64
|
"dependencies": {
|
|
65
65
|
"cachetrax": "^1.0.6",
|
|
66
66
|
"fable": "^3.1.72",
|
|
67
|
-
"pict-application": "^1.0.
|
|
68
|
-
"pict-provider": "^1.0.
|
|
67
|
+
"pict-application": "^1.0.34",
|
|
68
|
+
"pict-provider": "^1.0.13",
|
|
69
69
|
"pict-template": "^1.0.15",
|
|
70
70
|
"pict-view": "^1.0.68"
|
|
71
71
|
}
|
package/source/Pict.js
CHANGED
|
@@ -30,6 +30,7 @@ const libFable = require('fable');
|
|
|
30
30
|
const libPackage = require('../package.json');
|
|
31
31
|
const libProviderFilterManager = require('./providers/Provider-Filter-Manager.js');
|
|
32
32
|
const libProviderDataBroker = require('./providers/Provider-DataBroker.js');
|
|
33
|
+
const libProviderIcon = require('./providers/Provider-Icon.js');
|
|
33
34
|
|
|
34
35
|
const PictTemplateProvider = require('./Pict-Template-Provider.js');
|
|
35
36
|
const PictTemplateAudit = require('./Pict-Template-Audit.js');
|
|
@@ -185,6 +186,26 @@ class Pict extends libFable {
|
|
|
185
186
|
|
|
186
187
|
this.addProvider('FilterManager', {}, libProviderFilterManager);
|
|
187
188
|
this.addProvider('DataBroker', {}, libProviderDataBroker);
|
|
189
|
+
this.addProvider('Icon', {}, libProviderIcon);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Convenience accessor for the built-in icon registry — equivalent to
|
|
194
|
+
* `pict.providers.Icon.get(name, opts)`. Always returns a string
|
|
195
|
+
* (never throws, never undefined); a missing icon emits a visible
|
|
196
|
+
* question-mark glyph so the gap is obvious in the UI.
|
|
197
|
+
*
|
|
198
|
+
* @param {string} pName PascalCase icon name, or an alias.
|
|
199
|
+
* @param {object} [pOpts] { variant, size, class, ariaLabel }
|
|
200
|
+
* @returns {string}
|
|
201
|
+
*/
|
|
202
|
+
icon(pName, pOpts)
|
|
203
|
+
{
|
|
204
|
+
if (!this.providers || !this.providers.Icon)
|
|
205
|
+
{
|
|
206
|
+
return '';
|
|
207
|
+
}
|
|
208
|
+
return this.providers.Icon.get(pName, pOpts);
|
|
188
209
|
}
|
|
189
210
|
|
|
190
211
|
/**
|
|
@@ -533,6 +554,15 @@ class Pict extends libFable {
|
|
|
533
554
|
// Know the in-browser reference for Pict when appropriate
|
|
534
555
|
this.addTemplate(require(`./templates/Pict-Template-Self.js`));
|
|
535
556
|
|
|
557
|
+
// Built-in icon glyph by PascalCase name.
|
|
558
|
+
// {~Icon:Home~} default variant
|
|
559
|
+
// {~Icon:Folder:Filled~} explicit variant
|
|
560
|
+
// {~I:Spreadsheet~} short alias
|
|
561
|
+
// {~I:File:Filled~} short + variant
|
|
562
|
+
// Backed by pict.providers.Icon — host modules register
|
|
563
|
+
// additional glyphs via pict.providers.Icon.registerSet().
|
|
564
|
+
this.addTemplate(require(`./templates/Pict-Template-Icon.js`));
|
|
565
|
+
|
|
536
566
|
// Look up an entity template expression
|
|
537
567
|
// {~Entity:Book^AppData.Some.Address.IDBook^Render-Book-Template~}
|
|
538
568
|
// {~E:Book^AppData.Some.Address.IDBook^Render-Book-Template~}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Built-in icon set for Pict-Provider-Icon.
|
|
3
|
+
*
|
|
4
|
+
* Every glyph is a full <svg> string with `viewBox="0 0 24 24"` and uses
|
|
5
|
+
* `currentColor` for paint so it picks up surrounding text color (and
|
|
6
|
+
* therefore the active theme). Width/height are intentionally NOT set
|
|
7
|
+
* on the <svg> — the provider's CSS sizes them via `font-size` so
|
|
8
|
+
* consumers can scale with the natural font cascade or via an `em`-aware
|
|
9
|
+
* `style="font-size:20px"` from a one-off `pict.icon('X', { size: 20 })`.
|
|
10
|
+
*
|
|
11
|
+
* Variants:
|
|
12
|
+
* - Outline (default) — single-stroke, hollow. Lucide-style.
|
|
13
|
+
* - Filled — solid currentColor fill, no stroke. Used when
|
|
14
|
+
* the filled visual is meaningfully distinct
|
|
15
|
+
* (closed-shape icons mostly — see _DEFAULTS).
|
|
16
|
+
*
|
|
17
|
+
* Adding more variants (TwoTone, Duotone, custom illustrative sets) is
|
|
18
|
+
* just another key on the exported map plus a corresponding `registerSet`
|
|
19
|
+
* call from a host or section-module. No core change required.
|
|
20
|
+
*
|
|
21
|
+
* Naming convention: PascalCase canonical names. Aliases live in
|
|
22
|
+
* Provider-Icon.js (Gear -> Settings, House -> Home, X -> Close, etc.)
|
|
23
|
+
* so consumers don't fight over naming.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
const _OutlineDefaults =
|
|
27
|
+
{
|
|
28
|
+
// File system
|
|
29
|
+
'Folder': '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M3 7.5V18a1.5 1.5 0 0 0 1.5 1.5h15A1.5 1.5 0 0 0 21 18V9a1.5 1.5 0 0 0-1.5-1.5h-7L10.5 6H4.5A1.5 1.5 0 0 0 3 7.5z"/></svg>',
|
|
30
|
+
'FolderOpen': '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M3 7.5V18a1.5 1.5 0 0 0 1.5 1.5h15A1.5 1.5 0 0 0 21 18v-7H3"/><path d="M3 7.5h7L11.5 9h8A1.5 1.5 0 0 1 21 10.5"/></svg>',
|
|
31
|
+
'File': '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/></svg>',
|
|
32
|
+
'FileText': '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="8" y1="13" x2="16" y2="13"/><line x1="8" y1="17" x2="14" y2="17"/></svg>',
|
|
33
|
+
'Spreadsheet': '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="4" width="18" height="16" rx="1.5"/><line x1="3" y1="9" x2="21" y2="9"/><line x1="3" y1="14" x2="21" y2="14"/><line x1="9" y1="4" x2="9" y2="20"/></svg>',
|
|
34
|
+
'Image': '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="4" width="18" height="16" rx="2"/><circle cx="9" cy="10" r="1.5"/><path d="m21 16-5-5L7 20"/></svg>',
|
|
35
|
+
'Code': '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><polyline points="16 18 22 12 16 6"/><polyline points="8 6 2 12 8 18"/></svg>',
|
|
36
|
+
|
|
37
|
+
// Chevrons (open shapes — Outline only makes sense)
|
|
38
|
+
'ChevronUp': '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 15 12 9 18 15"/></svg>',
|
|
39
|
+
'ChevronDown': '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"/></svg>',
|
|
40
|
+
'ChevronLeft': '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><polyline points="15 6 9 12 15 18"/></svg>',
|
|
41
|
+
'ChevronRight': '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><polyline points="9 6 15 12 9 18"/></svg>',
|
|
42
|
+
|
|
43
|
+
// Arrows (open shapes)
|
|
44
|
+
'ArrowUp': '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><line x1="12" y1="19" x2="12" y2="5"/><polyline points="5 12 12 5 19 12"/></svg>',
|
|
45
|
+
'ArrowDown': '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><line x1="12" y1="5" x2="12" y2="19"/><polyline points="19 12 12 19 5 12"/></svg>',
|
|
46
|
+
'ArrowLeft': '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><line x1="19" y1="12" x2="5" y2="12"/><polyline points="12 19 5 12 12 5"/></svg>',
|
|
47
|
+
'ArrowRight': '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><line x1="5" y1="12" x2="19" y2="12"/><polyline points="12 5 19 12 12 19"/></svg>',
|
|
48
|
+
|
|
49
|
+
// Actions
|
|
50
|
+
'Save': '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z"/><polyline points="17 21 17 13 7 13 7 21"/><polyline points="7 3 7 8 15 8"/></svg>',
|
|
51
|
+
'Close': '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>',
|
|
52
|
+
'Check': '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg>',
|
|
53
|
+
'Plus': '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>',
|
|
54
|
+
'Minus': '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><line x1="5" y1="12" x2="19" y2="12"/></svg>',
|
|
55
|
+
'Edit': '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/><path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/></svg>',
|
|
56
|
+
'Trash': '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><polyline points="3 6 5 6 21 6"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/><line x1="10" y1="11" x2="10" y2="17"/><line x1="14" y1="11" x2="14" y2="17"/></svg>',
|
|
57
|
+
'Copy': '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg>',
|
|
58
|
+
'Share': '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><circle cx="18" cy="5" r="3"/><circle cx="6" cy="12" r="3"/><circle cx="18" cy="19" r="3"/><line x1="8.59" y1="13.51" x2="15.42" y2="17.49"/><line x1="15.41" y1="6.51" x2="8.59" y2="10.49"/></svg>',
|
|
59
|
+
'Search': '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>',
|
|
60
|
+
'Refresh':'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><polyline points="23 4 23 10 17 10"/><polyline points="1 20 1 14 7 14"/><path d="M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15"/></svg>',
|
|
61
|
+
'Download':'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg>',
|
|
62
|
+
'Upload': '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="17 8 12 3 7 8"/><line x1="12" y1="3" x2="12" y2="15"/></svg>',
|
|
63
|
+
'Link': '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/></svg>',
|
|
64
|
+
'ExternalLink':'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/><polyline points="15 3 21 3 21 9"/><line x1="10" y1="14" x2="21" y2="3"/></svg>',
|
|
65
|
+
|
|
66
|
+
// Status
|
|
67
|
+
'Info': '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><line x1="12" y1="16" x2="12" y2="12"/><line x1="12" y1="8" x2="12.01" y2="8"/></svg>',
|
|
68
|
+
'Warning': '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M10.29 3.86 1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg>',
|
|
69
|
+
'Error': '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><line x1="15" y1="9" x2="9" y2="15"/><line x1="9" y1="9" x2="15" y2="15"/></svg>',
|
|
70
|
+
'Success': '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"/><polyline points="22 4 12 14.01 9 11.01"/></svg>',
|
|
71
|
+
|
|
72
|
+
// UI chrome
|
|
73
|
+
'Home': '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M3 10.5 12 3l9 7.5V20a1 1 0 0 1-1 1h-5v-7h-6v7H4a1 1 0 0 1-1-1v-9.5z"/></svg>',
|
|
74
|
+
'Settings': '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 1 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06A1.65 1.65 0 0 0 4.6 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 1 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 1 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 1 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z"/></svg>',
|
|
75
|
+
'Menu': '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><line x1="3" y1="6" x2="21" y2="6"/><line x1="3" y1="12" x2="21" y2="12"/><line x1="3" y1="18" x2="21" y2="18"/></svg>',
|
|
76
|
+
'More': '<svg viewBox="0 0 24 24" fill="currentColor" stroke="none"><circle cx="5" cy="12" r="1.6"/><circle cx="12" cy="12" r="1.6"/><circle cx="19" cy="12" r="1.6"/></svg>',
|
|
77
|
+
'Eye': '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/><circle cx="12" cy="12" r="3"/></svg>',
|
|
78
|
+
'EyeOff': '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19m-6.72-1.07a3 3 0 1 1-4.24-4.24"/><line x1="1" y1="1" x2="23" y2="23"/></svg>',
|
|
79
|
+
'Lock': '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="11" width="18" height="11" rx="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/></svg>',
|
|
80
|
+
'User': '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/></svg>',
|
|
81
|
+
'Help': '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg>'
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
// Filled variants — only where the filled visual is meaningfully distinct
|
|
85
|
+
// from the outline. Open-shape icons (Chevrons, Arrows, Close, Plus) have
|
|
86
|
+
// no Filled variant; lookup falls back to Outline.
|
|
87
|
+
const _FilledVariants =
|
|
88
|
+
{
|
|
89
|
+
'Folder': '<svg viewBox="0 0 24 24" fill="currentColor" stroke="none"><path d="M3 7.5V18a1.5 1.5 0 0 0 1.5 1.5h15A1.5 1.5 0 0 0 21 18V9a1.5 1.5 0 0 0-1.5-1.5h-7L10.5 6H4.5A1.5 1.5 0 0 0 3 7.5z"/></svg>',
|
|
90
|
+
'FolderOpen': '<svg viewBox="0 0 24 24" fill="currentColor" stroke="none"><path d="M3 8a1 1 0 0 1 1-1h6.5l2 2H20a1 1 0 0 1 1 1v8.5A1.5 1.5 0 0 1 19.5 20h-15A1.5 1.5 0 0 1 3 18.5V8z" fill-opacity="0.92"/></svg>',
|
|
91
|
+
'File': '<svg viewBox="0 0 24 24" fill="currentColor" stroke="none"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8l-6-6zm-.5 7V3.5L19.5 9.5H14a.5.5 0 0 1-.5-.5z"/></svg>',
|
|
92
|
+
'FileText': '<svg viewBox="0 0 24 24" fill="currentColor" stroke="none"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8l-6-6zm-.5 7V3.5L19.5 9.5H14a.5.5 0 0 1-.5-.5zM8 12.4a.6.6 0 0 1 .6-.6h6.8a.6.6 0 0 1 0 1.2H8.6a.6.6 0 0 1-.6-.6zm0 4a.6.6 0 0 1 .6-.6h4.8a.6.6 0 0 1 0 1.2H8.6a.6.6 0 0 1-.6-.6z"/></svg>',
|
|
93
|
+
'Spreadsheet':'<svg viewBox="0 0 24 24" fill="currentColor" stroke="none"><path d="M4 4h16a1.5 1.5 0 0 1 1.5 1.5v13A1.5 1.5 0 0 1 20 20H4a1.5 1.5 0 0 1-1.5-1.5v-13A1.5 1.5 0 0 1 4 4zm5 1.2H4.2v3.6H9V5.2zm10.8 0H10.2v3.6h9.6V5.2zM9 9.7H4.2v3.6H9V9.7zm10.8 0H10.2v3.6h9.6V9.7zM9 14.2H4.2v4.6H9v-4.6zm10.8 0H10.2v4.6h9.6v-4.6z"/></svg>',
|
|
94
|
+
'Home': '<svg viewBox="0 0 24 24" fill="currentColor" stroke="none"><path d="M11.3 2.2a1 1 0 0 1 1.4 0l9 7.5a1 1 0 0 1 .3.7V20a1 1 0 0 1-1 1h-5a.5.5 0 0 1-.5-.5V14a.5.5 0 0 0-.5-.5h-5a.5.5 0 0 0-.5.5v6.5a.5.5 0 0 1-.5.5H4a1 1 0 0 1-1-1v-9.5a1 1 0 0 1 .3-.7l8-7.6z"/></svg>',
|
|
95
|
+
'Settings': '<svg viewBox="0 0 24 24" fill="currentColor" stroke="none"><path d="M19.43 12.98c.04-.32.07-.65.07-.98s-.03-.66-.07-.98l2.11-1.65a.5.5 0 0 0 .12-.64l-2-3.46a.5.5 0 0 0-.61-.22l-2.49 1a7.3 7.3 0 0 0-1.69-.98l-.38-2.65A.49.49 0 0 0 14 2h-4a.49.49 0 0 0-.49.42l-.38 2.65a7.6 7.6 0 0 0-1.69.98l-2.49-1a.51.51 0 0 0-.61.22l-2 3.46a.493.493 0 0 0 .12.64l2.11 1.65c-.04.32-.07.65-.07.98s.03.66.07.98l-2.11 1.65a.5.5 0 0 0-.12.64l2 3.46a.5.5 0 0 0 .61.22l2.49-1c.52.4 1.08.73 1.69.98l.38 2.65A.49.49 0 0 0 10 22h4a.49.49 0 0 0 .49-.42l.38-2.65c.61-.25 1.17-.59 1.69-.98l2.49 1c.23.09.49 0 .61-.22l2-3.46a.5.5 0 0 0-.12-.64l-2.11-1.65zM12 15.5A3.5 3.5 0 1 1 12 8.5a3.5 3.5 0 0 1 0 7z"/></svg>',
|
|
96
|
+
'Trash': '<svg viewBox="0 0 24 24" fill="currentColor" stroke="none"><path d="M9 3a1 1 0 0 0-1 1v1H4.5a.5.5 0 0 0 0 1H5v14a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V6h.5a.5.5 0 0 0 0-1H16V4a1 1 0 0 0-1-1H9zm1 5a.5.5 0 0 1 .5.5v9a.5.5 0 0 1-1 0v-9a.5.5 0 0 1 .5-.5zm4 0a.5.5 0 0 1 .5.5v9a.5.5 0 0 1-1 0v-9a.5.5 0 0 1 .5-.5z"/></svg>',
|
|
97
|
+
'User': '<svg viewBox="0 0 24 24" fill="currentColor" stroke="none"><circle cx="12" cy="7" r="4"/><path d="M4 21v-1a6 6 0 0 1 6-6h4a6 6 0 0 1 6 6v1H4z"/></svg>',
|
|
98
|
+
'Lock': '<svg viewBox="0 0 24 24" fill="currentColor" stroke="none"><path d="M12 2a5 5 0 0 0-5 5v3H6a2 2 0 0 0-2 2v9a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-9a2 2 0 0 0-2-2h-1V7a5 5 0 0 0-5-5zm-3 5a3 3 0 0 1 6 0v3H9V7z"/></svg>',
|
|
99
|
+
'Info': '<svg viewBox="0 0 24 24" fill="currentColor" stroke="none"><path d="M12 2a10 10 0 1 0 0 20 10 10 0 0 0 0-20zm-.99 5.99a1 1 0 1 1 2 0 1 1 0 0 1-2 0zM10.5 11h2a.5.5 0 0 1 .5.5v5a.5.5 0 0 1-1 0V12h-1.5a.5.5 0 0 1 0-1z"/></svg>',
|
|
100
|
+
'Warning': '<svg viewBox="0 0 24 24" fill="currentColor" stroke="none"><path d="M10.29 3.86 1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0zM11.5 9a.5.5 0 0 1 1 0v4a.5.5 0 0 1-1 0V9zM12 17.5a.9.9 0 1 1 0-1.8.9.9 0 0 1 0 1.8z"/></svg>'
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
module.exports =
|
|
104
|
+
{
|
|
105
|
+
Outline: _OutlineDefaults,
|
|
106
|
+
Filled: _FilledVariants
|
|
107
|
+
};
|
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
const libPictProvider = require('pict-provider');
|
|
2
|
+
const _BuiltInIcons = require('./Pict-Icons-Base.js');
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Pict-Provider-Icon — the central icon registry.
|
|
6
|
+
*
|
|
7
|
+
* Every glyph is an SVG string with `currentColor`-driven paint, so icons
|
|
8
|
+
* inherit text color from the surrounding context and re-skin automatically
|
|
9
|
+
* when the active theme changes. The provider also injects a small base
|
|
10
|
+
* stylesheet so `font-size` on a parent scales icons natively — no SVG
|
|
11
|
+
* width/height juggling.
|
|
12
|
+
*
|
|
13
|
+
* Public surface:
|
|
14
|
+
* pict.icon(name [, opts]) convenience shortcut
|
|
15
|
+
* pict.providers.Icon.get(name [, opts]) same, via the provider
|
|
16
|
+
* pict.providers.Icon.has(name [, variant])
|
|
17
|
+
* pict.providers.Icon.register(name, svg [, opts])
|
|
18
|
+
* pict.providers.Icon.registerSet(byVariant)
|
|
19
|
+
*
|
|
20
|
+
* Template tag (registered by Pict-Template-Icon.js):
|
|
21
|
+
* {~Icon:Home~} default variant
|
|
22
|
+
* {~Icon:Folder:Filled~} explicit variant
|
|
23
|
+
* {~I:Spreadsheet~} short alias
|
|
24
|
+
* {~I:File:Filled~} short + variant
|
|
25
|
+
*
|
|
26
|
+
* Extension pattern — pict-section-* modules call `registerSet()` at boot
|
|
27
|
+
* to plug in section-specific icons (file-type glyphs, language marks,
|
|
28
|
+
* etc.) without forking core.
|
|
29
|
+
*
|
|
30
|
+
* Lookup order on miss:
|
|
31
|
+
* 1. exact (name, variant)
|
|
32
|
+
* 2. (name, defaultVariant) for that name
|
|
33
|
+
* 3. (name, Outline) — the global default variant
|
|
34
|
+
* 4. inline question-mark glyph + log warning; never throws
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
const _DefaultProviderConfiguration =
|
|
38
|
+
{
|
|
39
|
+
ProviderIdentifier: 'Pict-Provider-Icon',
|
|
40
|
+
|
|
41
|
+
AutoInitialize: true,
|
|
42
|
+
AutoInitializeOrdinal: 0,
|
|
43
|
+
|
|
44
|
+
// Variant used when {~Icon:Name~} is asked for with no explicit
|
|
45
|
+
// variant *and* no per-name default exists.
|
|
46
|
+
DefaultVariant: 'Outline',
|
|
47
|
+
|
|
48
|
+
// Default extra class applied to every emitted icon wrapper. Apps
|
|
49
|
+
// override per-call via opts.class. Always also gets 'pict-icon'.
|
|
50
|
+
DefaultIconClass: 'pict-icon',
|
|
51
|
+
|
|
52
|
+
// Default font-size when caller passes { size: N }. Icons sized via
|
|
53
|
+
// `em` (1em x 1em on the inner <svg>) so font-size drives dimensions.
|
|
54
|
+
DefaultSize: null,
|
|
55
|
+
|
|
56
|
+
// Auto-register the bundled base icon set during construction. Apps
|
|
57
|
+
// that want a completely custom set can set this false and seed via
|
|
58
|
+
// registerSet() after construction.
|
|
59
|
+
RegisterBaseIcons: true
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
// PascalCase aliases — let consumers reach for the name they remember
|
|
63
|
+
// first. Resolved at lookup time, so registering 'Settings' makes 'Gear'
|
|
64
|
+
// resolve to it automatically. Kept short on purpose; expansive aliasing
|
|
65
|
+
// is the host's job, not core's.
|
|
66
|
+
const _Aliases =
|
|
67
|
+
{
|
|
68
|
+
Gear: 'Settings',
|
|
69
|
+
Cog: 'Settings',
|
|
70
|
+
House: 'Home',
|
|
71
|
+
X: 'Close',
|
|
72
|
+
Cross: 'Close',
|
|
73
|
+
Hamburger: 'Menu',
|
|
74
|
+
Pencil: 'Edit',
|
|
75
|
+
Bin: 'Trash',
|
|
76
|
+
Magnifier: 'Search',
|
|
77
|
+
Reload: 'Refresh',
|
|
78
|
+
Person: 'User',
|
|
79
|
+
Padlock: 'Lock'
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
// Minimal CSS — sizes the icon via the parent's font cascade, no SVG
|
|
83
|
+
// width/height baked in. Themes recolor through currentColor; consumers
|
|
84
|
+
// can layer additional class rules on top.
|
|
85
|
+
const _ProviderCSS = (
|
|
86
|
+
'.pict-icon { display: inline-flex; align-items: center; justify-content: center; vertical-align: -0.125em; flex-shrink: 0; line-height: 0; color: inherit; }' +
|
|
87
|
+
'.pict-icon > svg { width: 1em; height: 1em; display: block; }'
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
// Inline fallback emitted when a requested icon is missing entirely.
|
|
91
|
+
// Visible (not invisible) so the misconfiguration is obvious in the UI
|
|
92
|
+
// the first time it surfaces.
|
|
93
|
+
const _MissingGlyph = (
|
|
94
|
+
'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" ' +
|
|
95
|
+
'stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round">' +
|
|
96
|
+
'<circle cx="12" cy="12" r="10"/>' +
|
|
97
|
+
'<path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"/>' +
|
|
98
|
+
'<line x1="12" y1="17" x2="12.01" y2="17"/></svg>'
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
class PictProviderIcon extends libPictProvider
|
|
102
|
+
{
|
|
103
|
+
constructor(pFable, pOptions, pServiceHash)
|
|
104
|
+
{
|
|
105
|
+
let tmpOptions = Object.assign({}, _DefaultProviderConfiguration, pOptions);
|
|
106
|
+
super(pFable, tmpOptions, pServiceHash);
|
|
107
|
+
|
|
108
|
+
/** @type {Record<string, any>} */
|
|
109
|
+
this.options;
|
|
110
|
+
/** @type {import('../Pict.js')} */
|
|
111
|
+
this.pict;
|
|
112
|
+
|
|
113
|
+
// Registry shape: { [variant]: { [name]: svgString } }
|
|
114
|
+
this._registry = {};
|
|
115
|
+
|
|
116
|
+
// Per-name default variant. Set by the first register() call for
|
|
117
|
+
// that name unless a later call passes { default: true }.
|
|
118
|
+
this._defaultsByName = {};
|
|
119
|
+
|
|
120
|
+
// Register base icons (Outline + Filled) before anyone can ask
|
|
121
|
+
// for them.
|
|
122
|
+
if (this.options.RegisterBaseIcons)
|
|
123
|
+
{
|
|
124
|
+
this.registerSet(_BuiltInIcons);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Inject the base stylesheet. CSSMap dedupes by hash so this is
|
|
128
|
+
// safe even if multiple Pict instances register their own Icon
|
|
129
|
+
// providers (single-tenant in browsers, but worth honoring).
|
|
130
|
+
if (this.pict && this.pict.CSSMap && typeof (this.pict.CSSMap.addCSS) === 'function')
|
|
131
|
+
{
|
|
132
|
+
this.pict.CSSMap.addCSS('Pict-Provider-Icon-CSS', _ProviderCSS, 100);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Register a single glyph. First call for a (name) sets the default
|
|
138
|
+
* variant for that name unless an explicit variant is passed; subsequent
|
|
139
|
+
* calls add more variants without changing the default.
|
|
140
|
+
*
|
|
141
|
+
* @param {string} pName PascalCase icon name (e.g. 'FileFolder')
|
|
142
|
+
* @param {string} pSvg Full <svg>...</svg> string (no width/height)
|
|
143
|
+
* @param {object} [pOpts] { variant: 'Outline'|'Filled'|... , default: bool, force: bool }
|
|
144
|
+
* @returns {boolean} true on success, false if a (name, variant)
|
|
145
|
+
* already exists and { force: true } wasn't set.
|
|
146
|
+
*/
|
|
147
|
+
register(pName, pSvg, pOpts)
|
|
148
|
+
{
|
|
149
|
+
if (typeof (pName) !== 'string' || pName.length < 1)
|
|
150
|
+
{
|
|
151
|
+
this.log.warn('Pict-Provider-Icon.register requires a non-empty name.');
|
|
152
|
+
return false;
|
|
153
|
+
}
|
|
154
|
+
if (typeof (pSvg) !== 'string' || pSvg.indexOf('<svg') < 0)
|
|
155
|
+
{
|
|
156
|
+
this.log.warn('Pict-Provider-Icon.register requires an SVG string for [' + pName + '].');
|
|
157
|
+
return false;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
let tmpOpts = pOpts || {};
|
|
161
|
+
let tmpVariant = tmpOpts.variant || this.options.DefaultVariant;
|
|
162
|
+
let tmpForce = !!tmpOpts.force;
|
|
163
|
+
let tmpDefault = !!tmpOpts.default;
|
|
164
|
+
|
|
165
|
+
if (!this._registry[tmpVariant])
|
|
166
|
+
{
|
|
167
|
+
this._registry[tmpVariant] = {};
|
|
168
|
+
}
|
|
169
|
+
if (this._registry[tmpVariant][pName] && !tmpForce)
|
|
170
|
+
{
|
|
171
|
+
this.log.warn('Pict-Provider-Icon: refusing to overwrite [' + pName + ':' + tmpVariant
|
|
172
|
+
+ '] (pass { force: true } to replace).');
|
|
173
|
+
return false;
|
|
174
|
+
}
|
|
175
|
+
this._registry[tmpVariant][pName] = pSvg;
|
|
176
|
+
|
|
177
|
+
// First registration for this name → it becomes the default,
|
|
178
|
+
// regardless of which variant it was registered under. This is
|
|
179
|
+
// what "default is first registered" means in practice.
|
|
180
|
+
if (!this._defaultsByName[pName] || tmpDefault)
|
|
181
|
+
{
|
|
182
|
+
this._defaultsByName[pName] = tmpVariant;
|
|
183
|
+
}
|
|
184
|
+
return true;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Bulk register a whole icon set, keyed by variant.
|
|
189
|
+
*
|
|
190
|
+
* registerSet({ Outline: { Home: '<svg/>', Folder: '<svg/>' },
|
|
191
|
+
* Filled: { Home: '<svg/>' } });
|
|
192
|
+
*
|
|
193
|
+
* Per-name defaults follow registration order — the first variant
|
|
194
|
+
* containing a given name becomes that name's default.
|
|
195
|
+
*
|
|
196
|
+
* @param {Record<string, Record<string, string>>} pByVariant
|
|
197
|
+
* @param {object} [pOpts] { force: bool }
|
|
198
|
+
*/
|
|
199
|
+
registerSet(pByVariant, pOpts)
|
|
200
|
+
{
|
|
201
|
+
if (!pByVariant || typeof (pByVariant) !== 'object')
|
|
202
|
+
{
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
let tmpForce = !!(pOpts && pOpts.force);
|
|
206
|
+
let tmpVariants = Object.keys(pByVariant);
|
|
207
|
+
for (let v = 0; v < tmpVariants.length; v++)
|
|
208
|
+
{
|
|
209
|
+
let tmpVariant = tmpVariants[v];
|
|
210
|
+
let tmpIcons = pByVariant[tmpVariant];
|
|
211
|
+
if (!tmpIcons) continue;
|
|
212
|
+
let tmpNames = Object.keys(tmpIcons);
|
|
213
|
+
for (let n = 0; n < tmpNames.length; n++)
|
|
214
|
+
{
|
|
215
|
+
let tmpName = tmpNames[n];
|
|
216
|
+
this.register(tmpName, tmpIcons[tmpName], { variant: tmpVariant, force: tmpForce });
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* @param {string} pName
|
|
223
|
+
* @param {string} [pVariant]
|
|
224
|
+
* @returns {boolean}
|
|
225
|
+
*/
|
|
226
|
+
has(pName, pVariant)
|
|
227
|
+
{
|
|
228
|
+
let tmpResolved = _Aliases[pName] || pName;
|
|
229
|
+
if (pVariant)
|
|
230
|
+
{
|
|
231
|
+
return !!(this._registry[pVariant] && this._registry[pVariant][tmpResolved]);
|
|
232
|
+
}
|
|
233
|
+
return !!this._defaultsByName[tmpResolved];
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Return the wrapped icon HTML for `pName`. Always returns a string
|
|
238
|
+
* — never throws and never returns undefined, so it's safe to embed
|
|
239
|
+
* in template output unchecked.
|
|
240
|
+
*
|
|
241
|
+
* @param {string} pName PascalCase icon name, or an alias
|
|
242
|
+
* @param {object} [pOpts] { variant, size, class, ariaLabel }
|
|
243
|
+
* @returns {string}
|
|
244
|
+
*/
|
|
245
|
+
get(pName, pOpts)
|
|
246
|
+
{
|
|
247
|
+
let tmpName = (typeof (pName) === 'string') ? pName.trim() : '';
|
|
248
|
+
if (tmpName.length < 1)
|
|
249
|
+
{
|
|
250
|
+
this.log.warn('Pict-Provider-Icon.get called with empty name.');
|
|
251
|
+
return this._wrap(_MissingGlyph, pOpts);
|
|
252
|
+
}
|
|
253
|
+
// Alias resolution — Gear → Settings, etc.
|
|
254
|
+
let tmpResolved = _Aliases[tmpName] || tmpName;
|
|
255
|
+
let tmpOpts = pOpts || {};
|
|
256
|
+
let tmpRequestedVariant = tmpOpts.variant;
|
|
257
|
+
|
|
258
|
+
// 1. exact match for explicitly-requested variant
|
|
259
|
+
if (tmpRequestedVariant && this._registry[tmpRequestedVariant] && this._registry[tmpRequestedVariant][tmpResolved])
|
|
260
|
+
{
|
|
261
|
+
return this._wrap(this._registry[tmpRequestedVariant][tmpResolved], tmpOpts);
|
|
262
|
+
}
|
|
263
|
+
// 2. per-name default variant
|
|
264
|
+
let tmpNameDefault = this._defaultsByName[tmpResolved];
|
|
265
|
+
if (tmpNameDefault && this._registry[tmpNameDefault] && this._registry[tmpNameDefault][tmpResolved])
|
|
266
|
+
{
|
|
267
|
+
if (tmpRequestedVariant)
|
|
268
|
+
{
|
|
269
|
+
// Fell back from a requested-but-missing variant — log so
|
|
270
|
+
// the gap is visible in dev, but still emit a glyph.
|
|
271
|
+
this.log.trace('Pict-Provider-Icon: variant [' + tmpRequestedVariant + '] missing for ['
|
|
272
|
+
+ tmpResolved + '], using [' + tmpNameDefault + '].');
|
|
273
|
+
}
|
|
274
|
+
return this._wrap(this._registry[tmpNameDefault][tmpResolved], tmpOpts);
|
|
275
|
+
}
|
|
276
|
+
// 3. global default variant
|
|
277
|
+
let tmpGlobal = this.options.DefaultVariant;
|
|
278
|
+
if (this._registry[tmpGlobal] && this._registry[tmpGlobal][tmpResolved])
|
|
279
|
+
{
|
|
280
|
+
return this._wrap(this._registry[tmpGlobal][tmpResolved], tmpOpts);
|
|
281
|
+
}
|
|
282
|
+
// 4. missing — show a question-mark glyph so the gap surfaces
|
|
283
|
+
this.log.warn('Pict-Provider-Icon: unknown icon [' + tmpName + ']'
|
|
284
|
+
+ (tmpRequestedVariant ? ' (variant ' + tmpRequestedVariant + ')' : '') + '.');
|
|
285
|
+
return this._wrap(_MissingGlyph, tmpOpts);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* @private
|
|
290
|
+
* @param {string} pSvg
|
|
291
|
+
* @param {object} [pOpts] { size, class, ariaLabel }
|
|
292
|
+
* @returns {string}
|
|
293
|
+
*/
|
|
294
|
+
_wrap(pSvg, pOpts)
|
|
295
|
+
{
|
|
296
|
+
let tmpOpts = pOpts || {};
|
|
297
|
+
let tmpClass = this.options.DefaultIconClass;
|
|
298
|
+
if (typeof (tmpOpts.class) === 'string' && tmpOpts.class.length > 0)
|
|
299
|
+
{
|
|
300
|
+
tmpClass += ' ' + tmpOpts.class;
|
|
301
|
+
}
|
|
302
|
+
let tmpStyle = '';
|
|
303
|
+
if (typeof (tmpOpts.size) === 'number' && tmpOpts.size > 0)
|
|
304
|
+
{
|
|
305
|
+
tmpStyle = ' style="font-size:' + tmpOpts.size + 'px"';
|
|
306
|
+
}
|
|
307
|
+
else if (typeof (tmpOpts.size) === 'string' && tmpOpts.size.length > 0)
|
|
308
|
+
{
|
|
309
|
+
tmpStyle = ' style="font-size:' + tmpOpts.size + '"';
|
|
310
|
+
}
|
|
311
|
+
// aria-hidden default true — most icons are decorative. Caller
|
|
312
|
+
// passes ariaLabel to attach a label and flip aria-hidden off.
|
|
313
|
+
let tmpAria = ' aria-hidden="true"';
|
|
314
|
+
if (typeof (tmpOpts.ariaLabel) === 'string' && tmpOpts.ariaLabel.length > 0)
|
|
315
|
+
{
|
|
316
|
+
tmpAria = ' role="img" aria-label="' + tmpOpts.ariaLabel.replace(/"/g, '"') + '"';
|
|
317
|
+
}
|
|
318
|
+
return '<span class="' + tmpClass + '"' + tmpStyle + tmpAria + '>' + pSvg + '</span>';
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
module.exports = PictProviderIcon;
|
|
323
|
+
module.exports.default_configuration = _DefaultProviderConfiguration;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
const libPictTemplate = require('pict-template');
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Icon template tag — emits a themable SVG icon by PascalCase name.
|
|
5
|
+
*
|
|
6
|
+
* {~Icon:Home~} → default variant for 'Home'
|
|
7
|
+
* {~Icon:Folder:Filled~} → explicit variant
|
|
8
|
+
* {~I:Spreadsheet~} → short alias of {~Icon:~}
|
|
9
|
+
* {~I:File:Filled~} → short + variant
|
|
10
|
+
*
|
|
11
|
+
* The plugin is a thin shim over the `Icon` provider — all the registry
|
|
12
|
+
* + lookup + variant-fallback logic lives there. See Provider-Icon.js
|
|
13
|
+
* for registration, alias resolution, and per-name defaults.
|
|
14
|
+
*
|
|
15
|
+
* Sizing happens at the CSS layer: icons are wrapped in `<span class="pict-icon">`
|
|
16
|
+
* whose inner svg is `1em × 1em`. To resize, set `font-size` on the
|
|
17
|
+
* parent (e.g. a button) — no template-side sizing needed for the common
|
|
18
|
+
* case. One-off sizing still works through the JS API:
|
|
19
|
+
* `pict.icon('Save', { size: 20 })`.
|
|
20
|
+
*/
|
|
21
|
+
class PictTemplateProviderIcon extends libPictTemplate
|
|
22
|
+
{
|
|
23
|
+
/**
|
|
24
|
+
* @param {Object} pFable - The Fable Framework instance
|
|
25
|
+
* @param {Object} pOptions - The options for the service
|
|
26
|
+
* @param {String} pServiceHash - The hash of the service
|
|
27
|
+
*/
|
|
28
|
+
constructor(pFable, pOptions, pServiceHash)
|
|
29
|
+
{
|
|
30
|
+
super(pFable, pOptions, pServiceHash);
|
|
31
|
+
|
|
32
|
+
this.addPattern('{~Icon:', '~}');
|
|
33
|
+
this.addPattern('{~I:', '~}');
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* @param {string} pTemplateHash - Body of the tag (the bit between the
|
|
38
|
+
* opening pattern and the closing `~}`).
|
|
39
|
+
* Either "Name" or "Name:Variant".
|
|
40
|
+
* @returns {string}
|
|
41
|
+
*/
|
|
42
|
+
render(pTemplateHash)
|
|
43
|
+
{
|
|
44
|
+
let tmpBody = (pTemplateHash || '').trim();
|
|
45
|
+
if (tmpBody.length < 1)
|
|
46
|
+
{
|
|
47
|
+
return '';
|
|
48
|
+
}
|
|
49
|
+
let tmpName = tmpBody;
|
|
50
|
+
let tmpVariant = null;
|
|
51
|
+
let tmpColon = tmpBody.indexOf(':');
|
|
52
|
+
if (tmpColon > -1)
|
|
53
|
+
{
|
|
54
|
+
tmpName = tmpBody.slice(0, tmpColon).trim();
|
|
55
|
+
tmpVariant = tmpBody.slice(tmpColon + 1).trim();
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
let tmpProvider = this.pict && this.pict.providers && this.pict.providers.Icon;
|
|
59
|
+
if (!tmpProvider || typeof (tmpProvider.get) !== 'function')
|
|
60
|
+
{
|
|
61
|
+
// Provider missing — should never happen in a normally-booted
|
|
62
|
+
// Pict app, but degrade silently rather than emit something
|
|
63
|
+
// that looks like a template tag.
|
|
64
|
+
return '';
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
let tmpOpts = tmpVariant ? { variant: tmpVariant } : undefined;
|
|
68
|
+
return tmpProvider.get(tmpName, tmpOpts);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
module.exports = PictTemplateProviderIcon;
|