@swiftbrowser/web 0.1.0 → 0.3.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.
Files changed (99) hide show
  1. package/README.md +141 -63
  2. package/app/index.html +21 -10
  3. package/node/dist/node/apps.d.ts +16 -0
  4. package/node/dist/node/apps.d.ts.map +1 -1
  5. package/node/dist/node/apps.js +9 -0
  6. package/node/dist/node/apps.js.map +1 -1
  7. package/node/dist/node/config.d.ts +14 -1
  8. package/node/dist/node/config.d.ts.map +1 -1
  9. package/node/dist/node/config.js +20 -3
  10. package/node/dist/node/config.js.map +1 -1
  11. package/node/dist/node/examples.d.ts +2 -1
  12. package/node/dist/node/examples.d.ts.map +1 -1
  13. package/node/dist/node/examples.js +12 -2
  14. package/node/dist/node/examples.js.map +1 -1
  15. package/node/dist/plugins/app-manifests.d.ts +4 -1
  16. package/node/dist/plugins/app-manifests.d.ts.map +1 -1
  17. package/node/dist/plugins/app-manifests.js +3 -2
  18. package/node/dist/plugins/app-manifests.js.map +1 -1
  19. package/node/dist/plugins/asset-catalogs.d.ts +15 -3
  20. package/node/dist/plugins/asset-catalogs.d.ts.map +1 -1
  21. package/node/dist/plugins/asset-catalogs.js +111 -13
  22. package/node/dist/plugins/asset-catalogs.js.map +1 -1
  23. package/node/dist/plugins/included-sites.d.ts +26 -0
  24. package/node/dist/plugins/included-sites.d.ts.map +1 -0
  25. package/node/dist/plugins/included-sites.js +70 -0
  26. package/node/dist/plugins/included-sites.js.map +1 -0
  27. package/node/dist/plugins/pdfImages.d.ts +27 -0
  28. package/node/dist/plugins/pdfImages.d.ts.map +1 -0
  29. package/node/dist/plugins/pdfImages.js +145 -0
  30. package/node/dist/plugins/pdfImages.js.map +1 -0
  31. package/node/dist/plugins/swift-wasm.d.ts +19 -1
  32. package/node/dist/plugins/swift-wasm.d.ts.map +1 -1
  33. package/node/dist/plugins/swift-wasm.js +45 -7
  34. package/node/dist/plugins/swift-wasm.js.map +1 -1
  35. package/node/dist/plugins/symbolTemplates.d.ts +22 -0
  36. package/node/dist/plugins/symbolTemplates.d.ts.map +1 -0
  37. package/node/dist/plugins/symbolTemplates.js +83 -0
  38. package/node/dist/plugins/symbolTemplates.js.map +1 -0
  39. package/package.json +4 -2
  40. package/plugins/app-manifests.ts +3 -2
  41. package/plugins/asset-catalogs.ts +119 -14
  42. package/plugins/included-sites.ts +74 -0
  43. package/plugins/pdfImages.ts +163 -0
  44. package/plugins/swift-wasm.ts +52 -6
  45. package/plugins/symbolTemplates.ts +88 -0
  46. package/public/_headers +7 -0
  47. package/public/app/oauth-callback.html +62 -0
  48. package/src/animation.ts +235 -8
  49. package/src/appFiles.ts +201 -0
  50. package/src/base64.ts +43 -0
  51. package/src/bitmapPlan.ts +143 -0
  52. package/src/bitmapStore.ts +206 -0
  53. package/src/bitmapWorker.ts +41 -0
  54. package/src/bitmapWorkerProtocol.ts +15 -0
  55. package/src/bitmaps.ts +283 -0
  56. package/src/blockedHosts.ts +29 -0
  57. package/src/canvasDrawing.ts +361 -0
  58. package/src/chartDom.ts +2 -2
  59. package/src/chromeColor.ts +179 -0
  60. package/src/clipboard.ts +78 -0
  61. package/src/colorMultiply.ts +93 -0
  62. package/src/colorPicker.ts +44 -0
  63. package/src/corsProxy.ts +45 -0
  64. package/src/gaugeRing.ts +74 -0
  65. package/src/gif.ts +297 -0
  66. package/src/hostRequests.ts +426 -0
  67. package/src/imageOps.ts +592 -0
  68. package/src/interaction.ts +68 -3
  69. package/src/landing.ts +2 -2
  70. package/src/largeTitle.ts +79 -0
  71. package/src/layout/chart.ts +9 -3
  72. package/src/layout/engine.ts +681 -174
  73. package/src/layout/index.ts +3 -1
  74. package/src/layout/text.ts +217 -1
  75. package/src/layout/types.ts +31 -0
  76. package/src/layout/typography.ts +24 -7
  77. package/src/layout/video.ts +24 -0
  78. package/src/layoutDom.ts +66 -6
  79. package/src/main.ts +127 -4
  80. package/src/media.ts +132 -0
  81. package/src/mock.ts +4 -3
  82. package/src/notifications.ts +109 -0
  83. package/src/pathShape.ts +156 -0
  84. package/src/protocol.ts +782 -13
  85. package/src/renderer.ts +603 -44
  86. package/src/richText.ts +65 -0
  87. package/src/scanner.ts +234 -0
  88. package/src/scrollTo.ts +123 -0
  89. package/src/styles.css +521 -100
  90. package/src/styles.ts +61 -0
  91. package/src/swiftData.ts +128 -0
  92. package/src/symbols.ts +658 -28
  93. package/src/translation.ts +126 -0
  94. package/src/video.ts +299 -0
  95. package/src/vite-env.d.ts +6 -0
  96. package/src/wasm.ts +227 -32
  97. package/src/webAuth.ts +267 -0
  98. package/src/webSocket.ts +148 -0
  99. package/src/zoneinfo.ts +269 -0
package/src/symbols.ts CHANGED
@@ -14,9 +14,18 @@
14
14
 
15
15
  import {
16
16
  Accessibility,
17
+ Activity,
18
+ Airplay,
19
+ ALargeSmall,
17
20
  AlarmClock,
18
21
  Archive,
22
+ ArrowBigDown,
23
+ ArrowBigLeft,
24
+ ArrowBigRight,
25
+ ArrowBigUp,
19
26
  ArrowDown,
27
+ ArrowDownLeft,
28
+ ArrowDownRight,
20
29
  ArrowDownToLine,
21
30
  ArrowLeft,
22
31
  ArrowLeftRight,
@@ -24,35 +33,64 @@ import {
24
33
  ArrowUp,
25
34
  ArrowUpDown,
26
35
  ArrowUpFromLine,
36
+ ArrowUpLeft,
27
37
  ArrowUpRight,
28
38
  AtSign,
39
+ AudioWaveform,
40
+ Award,
41
+ BadgeCheck,
42
+ BadgeQuestionMark,
43
+ Ban,
44
+ Bandage,
45
+ Banknote,
46
+ Barcode,
29
47
  Battery,
48
+ BatteryFull,
49
+ BatteryLow,
50
+ BatteryMedium,
51
+ BatteryWarning,
52
+ BedDouble,
30
53
  Bell,
54
+ BellDot,
55
+ BellOff,
31
56
  Bike,
57
+ Bird,
58
+ Bitcoin,
32
59
  Bluetooth,
60
+ Bold,
61
+ CaseSensitive,
33
62
  Book,
34
- BookOpen,
35
63
  Bookmark,
64
+ BookOpen,
65
+ Brain,
36
66
  Briefcase,
67
+ BriefcaseMedical,
37
68
  Bug,
38
69
  Building,
39
70
  Bus,
71
+ Cake,
40
72
  Calendar,
73
+ CalendarClock,
74
+ CalendarPlus,
75
+ CalendarX,
41
76
  Camera,
42
77
  Car,
78
+ Carrot,
79
+ Cat,
43
80
  ChartBar,
81
+ ChartColumn,
44
82
  ChartLine,
45
83
  ChartPie,
46
84
  Check,
47
85
  ChevronDown,
48
86
  ChevronLeft,
49
87
  ChevronRight,
50
- ChevronUp,
51
88
  ChevronsDown,
52
89
  ChevronsLeft,
53
90
  ChevronsRight,
54
91
  ChevronsUp,
55
92
  ChevronsUpDown,
93
+ ChevronUp,
56
94
  Circle,
57
95
  CircleAlert,
58
96
  CircleArrowDown,
@@ -60,152 +98,326 @@ import {
60
98
  CircleArrowRight,
61
99
  CircleArrowUp,
62
100
  CircleCheck,
101
+ CircleDollarSign,
63
102
  CircleDot,
64
103
  CircleEllipsis,
65
104
  CircleMinus,
66
105
  CirclePause,
67
106
  CirclePlay,
68
107
  CirclePlus,
69
- CircleStop,
70
108
  CircleQuestionMark,
109
+ CircleStop,
71
110
  CircleUser,
72
111
  CircleX,
112
+ Clapperboard,
73
113
  Clipboard,
114
+ ClipboardList,
74
115
  Clock,
116
+ ClockCheck,
75
117
  Cloud,
118
+ CloudDownload,
119
+ CloudDrizzle,
120
+ CloudFog,
121
+ CloudHail,
76
122
  CloudLightning,
77
123
  CloudMoon,
78
124
  CloudRain,
125
+ CloudRainWind,
79
126
  CloudSnow,
80
127
  CloudSun,
128
+ CloudUpload,
81
129
  Code,
130
+ Coffee,
82
131
  Cog,
132
+ Columns2,
133
+ Command,
83
134
  Compass,
135
+ Contact,
84
136
  Copy,
137
+ CornerDownLeft,
85
138
  CreditCard,
139
+ Crop,
140
+ Cross,
86
141
  Crown,
142
+ Database,
143
+ Delete,
144
+ Diamond,
145
+ Dices,
146
+ Dog,
147
+ DollarSign,
87
148
  Download,
88
149
  Droplet,
150
+ Droplets,
151
+ Dumbbell,
152
+ Earth,
89
153
  Ellipsis,
154
+ Euro,
90
155
  Eye,
91
156
  EyeOff,
92
157
  FaceSlightlySmiling,
93
158
  File,
159
+ FilePlus,
94
160
  FileText,
161
+ Film,
162
+ Fingerprint,
163
+ Fish,
95
164
  Flag,
96
165
  Flame,
97
166
  Folder,
167
+ FolderMinus,
168
+ FolderPlus,
169
+ Forward,
170
+ Fuel,
98
171
  Funnel,
172
+ GalleryVerticalEnd,
99
173
  Gamepad2,
174
+ Gauge,
100
175
  Gift,
176
+ GitBranch,
177
+ GitMerge,
178
+ Glasses,
101
179
  Globe,
102
180
  GraduationCap,
181
+ Grid3x3,
182
+ Hammer,
103
183
  Hand,
104
- Hourglass,
184
+ HardDrive,
185
+ Hash,
105
186
  Headphones,
106
187
  Heart,
188
+ HeartOff,
189
+ Hexagon,
190
+ Highlighter,
191
+ History,
192
+ Hourglass,
107
193
  House,
194
+ IdCard,
108
195
  Image,
196
+ Images,
109
197
  Inbox,
198
+ Infinity,
110
199
  Info,
200
+ Italic,
201
+ JapaneseYen,
111
202
  Key,
112
203
  Keyboard,
204
+ Landmark,
113
205
  Laptop,
206
+ Lasso,
207
+ Layers,
208
+ Layers3,
209
+ LayoutGrid,
210
+ LayoutList,
114
211
  Leaf,
212
+ Library,
115
213
  Lightbulb,
116
214
  Link,
215
+ Link2,
117
216
  List,
118
217
  ListChecks,
218
+ ListIndentIncrease,
219
+ ListMusic,
119
220
  ListOrdered,
120
221
  LoaderCircle,
222
+ LocateFixed,
121
223
  Lock,
122
224
  LockOpen,
123
225
  LogIn,
124
226
  LogOut,
125
227
  Mail,
228
+ MailOpen,
229
+ MailWarning,
126
230
  Map,
127
231
  MapPin,
232
+ MapPinOff,
233
+ Maximize2,
234
+ Medal,
128
235
  Menu,
129
236
  MessageCircle,
130
237
  MessageSquare,
238
+ MessageSquareMore,
239
+ MessageSquarePlus,
240
+ MessageSquareQuote,
241
+ MessageSquareText,
242
+ MessageSquareWarning,
243
+ MessagesSquare,
131
244
  Mic,
132
245
  MicOff,
246
+ MicVocal,
247
+ Minimize2,
133
248
  Minus,
134
249
  Monitor,
250
+ MonitorPlay,
135
251
  Moon,
252
+ Mountain,
136
253
  Music,
254
+ Navigation2,
255
+ Network,
256
+ Newspaper,
257
+ Octagon,
258
+ OctagonAlert,
259
+ OctagonX,
260
+ Option,
137
261
  Package,
262
+ Paintbrush,
263
+ Palette,
264
+ PanelLeft,
265
+ PanelRight,
138
266
  Paperclip,
139
267
  Pause,
268
+ PawPrint,
140
269
  Pencil,
270
+ PencilOff,
271
+ PenTool,
272
+ Percent,
273
+ PersonStanding,
141
274
  Phone,
275
+ PhoneIncoming,
276
+ PhoneOff,
277
+ PhoneOutgoing,
278
+ Pill,
142
279
  Pin,
280
+ Pipette,
143
281
  Plane,
144
282
  Play,
145
283
  Plus,
284
+ Pointer,
285
+ PoundSterling,
146
286
  Power,
147
287
  Printer,
288
+ Puzzle,
148
289
  QrCode,
290
+ Rabbit,
291
+ Radio,
292
+ RectangleHorizontal,
293
+ RectangleVertical,
294
+ Redo2,
149
295
  RefreshCw,
150
296
  Repeat,
297
+ Reply,
298
+ ReplyAll,
151
299
  RotateCcw,
152
300
  RotateCw,
301
+ Rows2,
302
+ Ruler,
303
+ Sailboat,
304
+ Scan,
305
+ ScanBarcode,
306
+ ScanFace,
307
+ ScanQrCode,
308
+ School,
309
+ Scissors,
153
310
  Search,
154
311
  Send,
312
+ Server,
155
313
  Settings,
314
+ Shapes,
156
315
  Share,
316
+ Share2,
157
317
  Shield,
158
318
  ShieldCheck,
319
+ Ship,
159
320
  ShoppingBag,
321
+ ShoppingBasket,
160
322
  ShoppingCart,
161
323
  Shuffle,
324
+ Sigma,
325
+ Signal,
326
+ Signpost,
162
327
  SkipBack,
163
328
  SkipForward,
164
329
  SlidersHorizontal,
165
330
  Smartphone,
331
+ Snowflake,
332
+ Sparkle,
166
333
  Sparkles,
167
334
  Speaker,
168
335
  Square,
336
+ SquareArrowDown,
337
+ SquareArrowLeft,
338
+ SquareArrowOutUpRight,
339
+ SquareArrowRight,
340
+ SquareArrowUp,
169
341
  SquareCheck,
342
+ SquareMinus,
343
+ SquareParking,
170
344
  SquarePen,
345
+ SquarePlay,
346
+ SquarePlus,
347
+ SquareUser,
348
+ SquareX,
171
349
  Star,
350
+ StarHalf,
351
+ StarOff,
352
+ Stethoscope,
353
+ StickyNote,
354
+ Store,
355
+ Strikethrough,
172
356
  Sun,
357
+ SunDim,
358
+ Sunrise,
359
+ Sunset,
360
+ SwitchCamera,
173
361
  Tag,
174
362
  Target,
363
+ Tent,
175
364
  Terminal,
176
- Timer,
365
+ TextAlignCenter,
366
+ TextAlignEnd,
367
+ TextAlignJustify,
368
+ TextAlignStart,
369
+ Thermometer,
370
+ ThumbsDown,
177
371
  ThumbsUp,
372
+ Ticket,
373
+ Timer,
374
+ Tornado,
375
+ TrainFront,
376
+ TramFront,
178
377
  Trash,
179
- TriangleAlert,
378
+ Trash2,
379
+ TreeDeciduous,
380
+ TrendingDown,
180
381
  TrendingUp,
382
+ Triangle,
383
+ TriangleAlert,
181
384
  Trophy,
385
+ Truck,
386
+ Turtle,
182
387
  Tv,
183
388
  Type,
389
+ Umbrella,
390
+ Underline,
391
+ Undo2,
184
392
  Upload,
185
393
  User,
394
+ UserCheck,
395
+ UserMinus,
396
+ UserPlus,
186
397
  Users,
398
+ UserX,
399
+ Utensils,
400
+ Video,
401
+ VideoOff,
187
402
  Volume,
188
403
  Volume1,
189
404
  Volume2,
190
405
  VolumeX,
191
406
  Wallet,
407
+ WandSparkles,
192
408
  Watch,
193
409
  Wifi,
410
+ WifiOff,
194
411
  Wind,
412
+ Wine,
195
413
  Wrench,
196
414
  X,
197
415
  Zap,
416
+ ZapOff,
198
417
  ZoomIn,
199
418
  ZoomOut,
200
419
  createElement,
201
420
  type IconNode,
202
- Grid3x3,
203
- LayoutGrid,
204
- LayoutList,
205
- MessageSquarePlus,
206
- MessageSquareText,
207
- Truck,
208
- Utensils,
209
421
  } from 'lucide';
210
422
 
211
423
  /** How a glyph is painted; see the file comment. */
@@ -216,6 +428,41 @@ export interface SymbolGlyph {
216
428
  fill: GlyphFill;
217
429
  }
218
430
 
431
+ /**
432
+ * The activity indicator (an indeterminate `ProgressView`), as iOS draws it:
433
+ * eight rounded spokes filling the glyph, the leading one (at the top) at full
434
+ * strength and the three behind it fading out to the rest's 0.31. styles.css
435
+ * steps that ramp around the spokes clockwise (the glyph itself stays still),
436
+ * so the fade trails the leading spoke.
437
+ */
438
+ const ACTIVITY_INDICATOR: IconNode = [
439
+ ['line', { x1: '12', y1: '6.21', x2: '12', y2: '1.52', 'stroke-width': '3.05', 'stroke-opacity': '1' }],
440
+ ['line', { x1: '16.09', y1: '7.91', x2: '19.41', y2: '4.59', 'stroke-width': '3.05', 'stroke-opacity': '0.31' }],
441
+ ['line', { x1: '17.79', y1: '12', x2: '22.48', y2: '12', 'stroke-width': '3.05', 'stroke-opacity': '0.31' }],
442
+ ['line', { x1: '16.09', y1: '16.09', x2: '19.41', y2: '19.41', 'stroke-width': '3.05', 'stroke-opacity': '0.31' }],
443
+ ['line', { x1: '12', y1: '17.79', x2: '12', y2: '22.48', 'stroke-width': '3.05', 'stroke-opacity': '0.31' }],
444
+ ['line', { x1: '7.91', y1: '16.09', x2: '4.59', y2: '19.41', 'stroke-width': '3.05', 'stroke-opacity': '0.49' }],
445
+ ['line', { x1: '6.21', y1: '12', x2: '1.52', y2: '12', 'stroke-width': '3.05', 'stroke-opacity': '0.66' }],
446
+ ['line', { x1: '7.91', y1: '7.91', x2: '4.59', y2: '4.59', 'stroke-width': '3.05', 'stroke-opacity': '0.83' }],
447
+ ];
448
+
449
+ /** `books.vertical.fill`: three upright books and a leaning one, as solid shapes. */
450
+ const BOOKS_VERTICAL_FILL: IconNode = [
451
+ ['rect', { x: '1.5', y: '3', width: '5', height: '18', rx: '1.2', stroke: 'none' }],
452
+ ['rect', { x: '7.5', y: '3', width: '5', height: '18', rx: '1.2', stroke: 'none' }],
453
+ ['rect', { x: '13.5', y: '5', width: '4', height: '16', rx: '1.2', stroke: 'none' }],
454
+ ['rect', { x: '17.8', y: '4.5', width: '4', height: '16', rx: '1.2', stroke: 'none', transform: 'rotate(-14 19.8 12.5)' }],
455
+ ];
456
+
457
+ /** Color used for inner marks of `knockout` glyphs; styles.css sets the variable. */
458
+ const KNOCKOUT_COLOR = 'var(--sb-icon-knockout, var(--sb-color-system-background, #fff))';
459
+
460
+ /** `square.on.circle.fill`: a solid square over a solid circle, a gap between them. */
461
+ const SQUARE_ON_CIRCLE_FILL: IconNode = [
462
+ ['circle', { cx: '9.5', cy: '9.5', r: '7.5' }],
463
+ ['rect', { x: '9', y: '9', width: '13', height: '13', rx: '3', stroke: KNOCKOUT_COLOR, 'stroke-width': '2.4' }],
464
+ ];
465
+
219
466
  const outline = (icon: IconNode): SymbolGlyph => ({ icon, fill: 'outline' });
220
467
  const knockout = (icon: IconNode): SymbolGlyph => ({ icon, fill: 'knockout' });
221
468
  const solid = (icon: IconNode): SymbolGlyph => ({ icon, fill: 'solid' });
@@ -322,7 +569,7 @@ export const SF_SYMBOLS: Readonly<Record<string, SymbolGlyph>> = {
322
569
  'flag.fill': solid(Flag),
323
570
  tag: outline(Tag),
324
571
  'tag.fill': solid(Tag),
325
- textformat: outline(Type),
572
+ textformat: outline(CaseSensitive),
326
573
  'line.3.horizontal': outline(Menu),
327
574
  'line.3.horizontal.decrease': outline(Funnel),
328
575
  'line.3.horizontal.decrease.circle': outline(Funnel),
@@ -338,7 +585,7 @@ export const SF_SYMBOLS: Readonly<Record<string, SymbolGlyph>> = {
338
585
 
339
586
  // People and places
340
587
  person: outline(User),
341
- 'person.fill': outline(User),
588
+ 'person.fill': solid(User),
342
589
  'person.2': outline(Users),
343
590
  'person.circle': outline(CircleUser),
344
591
  'person.circle.fill': knockout(CircleUser),
@@ -398,7 +645,7 @@ export const SF_SYMBOLS: Readonly<Record<string, SymbolGlyph>> = {
398
645
  camera: outline(Camera),
399
646
  'camera.fill': outline(Camera),
400
647
  photo: outline(Image),
401
- 'photo.fill': outline(Image),
648
+ 'photo.fill': knockout(Image),
402
649
  'photo.on.rectangle': outline(Image),
403
650
  play: outline(Play),
404
651
  'play.fill': solid(Play),
@@ -458,7 +705,7 @@ export const SF_SYMBOLS: Readonly<Record<string, SymbolGlyph>> = {
458
705
  'doc.text.fill': outline(FileText),
459
706
  archivebox: outline(Archive),
460
707
  'shippingbox': outline(Package),
461
- 'shippingbox.fill': outline(Package),
708
+ 'shippingbox.fill': knockout(Package),
462
709
 
463
710
  // Settings and security
464
711
  gear: outline(Settings),
@@ -494,7 +741,7 @@ export const SF_SYMBOLS: Readonly<Record<string, SymbolGlyph>> = {
494
741
  crown: outline(Crown),
495
742
  'crown.fill': solid(Crown),
496
743
  trophy: outline(Trophy),
497
- 'trophy.fill': outline(Trophy),
744
+ 'trophy.fill': solid(Trophy),
498
745
 
499
746
  // Nature and weather
500
747
  'sun.max': outline(Sun),
@@ -517,6 +764,7 @@ export const SF_SYMBOLS: Readonly<Record<string, SymbolGlyph>> = {
517
764
  'circle.grid.3x3': outline(Grid3x3),
518
765
  'fork.knife': outline(Utensils),
519
766
  'text.bubble': outline(MessageSquareText),
767
+ 'text.bubble.fill': knockout(MessageSquareText),
520
768
  'plus.bubble': outline(MessageSquarePlus),
521
769
  'box.truck': outline(Truck),
522
770
  'list.bullet.below.rectangle': outline(LayoutList),
@@ -548,20 +796,398 @@ export const SF_SYMBOLS: Readonly<Record<string, SymbolGlyph>> = {
548
796
 
549
797
  // Activity
550
798
  'rays': outline(LoaderCircle),
799
+ // An indeterminate ProgressView's spinner (eight spokes; styles.css animates their fade).
800
+ 'progress.indicator': outline(ACTIVITY_INDICATOR),
551
801
  'circle.dotted': outline(LoaderCircle),
802
+
803
+ // More common symbols: people, documents, arrows, text, shapes, money,
804
+ // health, travel, weather, nature, media, devices, tools.
805
+ 'bed.double': outline(BedDouble),
806
+ 'bed.double.fill': solid(BedDouble),
807
+ 'qrcode.viewfinder': outline(ScanQrCode),
808
+ barcode: outline(Barcode),
809
+ 'barcode.viewfinder': outline(ScanBarcode),
810
+ viewfinder: outline(Scan),
811
+ 'questionmark.diamond': outline(BadgeQuestionMark),
812
+ 'rectangle.on.rectangle': outline(Copy),
813
+ 'rectangle.on.rectangle.angled': outline(GalleryVerticalEnd),
814
+ 'person.badge.plus': outline(UserPlus),
815
+ 'person.badge.minus': outline(UserMinus),
816
+ 'person.crop.circle.badge.plus': outline(UserPlus),
817
+ 'person.crop.circle.badge.xmark': outline(UserX),
818
+ 'person.crop.circle.badge.checkmark': outline(UserCheck),
819
+ 'person.crop.circle.fill.badge.checkmark': outline(UserCheck),
820
+ 'person.crop.square': outline(SquareUser),
821
+ 'person.crop.square.fill': knockout(SquareUser),
822
+ 'person.2.fill': solid(Users),
823
+ 'person.2.circle': outline(Users),
824
+ 'person.text.rectangle': outline(IdCard),
825
+ 'person.crop.rectangle': outline(Contact),
826
+ 'folder.badge.plus': outline(FolderPlus),
827
+ 'folder.badge.minus': outline(FolderMinus),
828
+ 'doc.badge.plus': outline(FilePlus),
829
+ 'doc.on.doc.fill': outline(Copy),
830
+ 'calendar.badge.plus': outline(CalendarPlus),
831
+ 'calendar.badge.clock': outline(CalendarClock),
832
+ 'calendar.badge.exclamationmark': outline(CalendarX),
833
+ 'clock.arrow.circlepath': outline(History),
834
+ 'clock.badge.checkmark': outline(ClockCheck),
835
+ 'arrow.uturn.backward': outline(Undo2),
836
+ 'arrow.uturn.forward': outline(Redo2),
837
+ 'arrow.uturn.left': outline(Undo2),
838
+ 'arrow.uturn.right': outline(Redo2),
839
+ 'arrow.up.left.and.arrow.down.right': outline(Maximize2),
840
+ 'arrow.down.right.and.arrow.up.left': outline(Minimize2),
841
+ 'arrow.up.right.square': outline(SquareArrowOutUpRight),
842
+ 'arrow.up.forward.app': outline(SquareArrowOutUpRight),
843
+ 'arrow.right.square': outline(SquareArrowRight),
844
+ 'arrow.left.square': outline(SquareArrowLeft),
845
+ 'arrow.up.square': outline(SquareArrowUp),
846
+ 'arrow.down.square': outline(SquareArrowDown),
847
+ 'arrow.triangle.branch': outline(GitBranch),
848
+ 'arrow.triangle.merge': outline(GitMerge),
849
+ 'arrow.triangle.swap': outline(ArrowLeftRight),
850
+ 'arrow.up.and.down': outline(ArrowUpDown),
851
+ 'arrow.left.and.right': outline(ArrowLeftRight),
852
+ 'arrow.down.left': outline(ArrowDownLeft),
853
+ 'arrow.down.right': outline(ArrowDownRight),
854
+ 'arrow.up.left': outline(ArrowUpLeft),
855
+ 'arrowshape.turn.up.left': outline(Reply),
856
+ 'arrowshape.turn.up.right': outline(Forward),
857
+ 'arrowshape.turn.up.left.2': outline(ReplyAll),
858
+ 'arrowshape.left': outline(ArrowBigLeft),
859
+ 'arrowshape.right': outline(ArrowBigRight),
860
+ 'arrowshape.up': outline(ArrowBigUp),
861
+ 'arrowshape.down': outline(ArrowBigDown),
862
+ 'square.on.square': outline(Copy),
863
+ 'square.on.circle': outline(Shapes),
864
+ 'square.on.circle.fill': solid(SQUARE_ON_CIRCLE_FILL),
865
+ 'plus.square': outline(SquarePlus),
866
+ 'plus.square.fill': knockout(SquarePlus),
867
+ 'minus.square': outline(SquareMinus),
868
+ 'minus.square.fill': knockout(SquareMinus),
869
+ 'xmark.square': outline(SquareX),
870
+ 'xmark.square.fill': knockout(SquareX),
871
+ 'plus.app': outline(SquarePlus),
872
+ 'star.circle': outline(Star),
873
+ 'star.leadinghalf.filled': outline(StarHalf),
874
+ 'star.slash': outline(StarOff),
875
+ 'heart.slash': outline(HeartOff),
876
+ 'heart.circle': outline(Heart),
877
+ 'bell.slash': outline(BellOff),
878
+ 'bell.badge': outline(BellDot),
879
+ 'bell.badge.fill': outline(BellDot),
880
+ 'envelope.badge': outline(MailWarning),
881
+ 'envelope.open.fill': outline(MailOpen),
882
+ 'tray.full': outline(Inbox),
883
+ 'tray.fill': solid(Inbox),
884
+ 'tray.and.arrow.down': outline(Inbox),
885
+ 'tray.and.arrow.up': outline(Inbox),
886
+ 'phone.arrow.up.right': outline(PhoneOutgoing),
887
+ 'phone.arrow.down.left': outline(PhoneIncoming),
888
+ 'phone.down': outline(PhoneOff),
889
+ video: outline(Video),
890
+ 'video.fill': solid(Video),
891
+ 'video.slash': outline(VideoOff),
892
+ 'bubble.left.and.bubble.right': outline(MessagesSquare),
893
+ 'bubble.left.and.bubble.right.fill': outline(MessagesSquare),
894
+ 'bubble.right': outline(MessageSquare),
895
+ 'bubble.left.fill': solid(MessageSquare),
896
+ 'quote.bubble': outline(MessageSquareQuote),
897
+ 'ellipsis.bubble': outline(MessageSquareMore),
898
+ 'exclamationmark.bubble': outline(MessageSquareWarning),
899
+ 'text.alignleft': outline(TextAlignStart),
900
+ 'text.aligncenter': outline(TextAlignCenter),
901
+ 'text.alignright': outline(TextAlignEnd),
902
+ 'text.justify': outline(TextAlignJustify),
903
+ bold: outline(Bold),
904
+ italic: outline(Italic),
905
+ underline: outline(Underline),
906
+ strikethrough: outline(Strikethrough),
907
+ 'textformat.size': outline(ALargeSmall),
908
+ character: outline(Type),
909
+ 'list.dash': outline(List),
910
+ 'list.bullet.indent': outline(ListIndentIncrease),
911
+ 'list.clipboard': outline(ClipboardList),
912
+ 'square.grid.3x3': outline(Grid3x3),
913
+ 'square.grid.2x2.fill': solid(LayoutGrid),
914
+ 'rectangle.grid.1x2': outline(Rows2),
915
+ 'rectangle.split.2x1': outline(Columns2),
916
+ 'sidebar.left': outline(PanelLeft),
917
+ 'sidebar.right': outline(PanelRight),
918
+ 'rectangle.stack': outline(Layers),
919
+ 'square.stack': outline(Layers),
920
+ 'square.stack.3d.up': outline(Layers3),
921
+ rectangle: outline(RectangleHorizontal),
922
+ 'rectangle.fill': solid(RectangleHorizontal),
923
+ 'rectangle.portrait': outline(RectangleVertical),
924
+ triangle: outline(Triangle),
925
+ 'triangle.fill': solid(Triangle),
926
+ diamond: outline(Diamond),
927
+ 'diamond.fill': solid(Diamond),
928
+ hexagon: outline(Hexagon),
929
+ 'hexagon.fill': solid(Hexagon),
930
+ octagon: outline(Octagon),
931
+ 'checkmark.seal': outline(BadgeCheck),
932
+ 'checkmark.seal.fill': knockout(BadgeCheck),
933
+ 'xmark.octagon': outline(OctagonX),
934
+ 'xmark.octagon.fill': outline(OctagonX),
935
+ 'exclamationmark.octagon': outline(OctagonAlert),
936
+ nosign: outline(Ban),
937
+ 'slash.circle': outline(Ban),
938
+ number: outline(Hash),
939
+ percent: outline(Percent),
940
+ dollarsign: outline(DollarSign),
941
+ 'dollarsign.circle': outline(CircleDollarSign),
942
+ 'dollarsign.circle.fill': knockout(CircleDollarSign),
943
+ eurosign: outline(Euro),
944
+ sterlingsign: outline(PoundSterling),
945
+ yensign: outline(JapaneseYen),
946
+ bitcoinsign: outline(Bitcoin),
947
+ banknote: outline(Banknote),
948
+ basket: outline(ShoppingBasket),
949
+ storefront: outline(Store),
950
+ 'building.columns': outline(Landmark),
951
+ ticket: outline(Ticket),
952
+ 'chart.bar.fill': outline(ChartBar),
953
+ 'chart.bar.xaxis': outline(ChartColumn),
954
+ 'chart.line.downtrend.xyaxis': outline(TrendingDown),
955
+ waveform: outline(AudioWaveform),
956
+ 'waveform.path.ecg': outline(Activity),
957
+ cross: outline(Cross),
958
+ 'cross.case': outline(BriefcaseMedical),
959
+ pills: outline(Pill),
960
+ bandage: outline(Bandage),
961
+ stethoscope: outline(Stethoscope),
962
+ brain: outline(Brain),
963
+ 'brain.head.profile': outline(Brain),
964
+ 'figure.stand': outline(PersonStanding),
965
+ dumbbell: outline(Dumbbell),
966
+ tram: outline(TramFront),
967
+ 'train.side.front.car': outline(TrainFront),
968
+ ferry: outline(Ship),
969
+ sailboat: outline(Sailboat),
970
+ fuelpump: outline(Fuel),
971
+ parkingsign: outline(SquareParking),
972
+ tent: outline(Tent),
973
+ 'mountain.2': outline(Mountain),
974
+ tree: outline(TreeDeciduous),
975
+ thermometer: outline(Thermometer),
976
+ 'thermometer.medium': outline(Thermometer),
977
+ umbrella: outline(Umbrella),
978
+ snowflake: outline(Snowflake),
979
+ 'sun.min': outline(SunDim),
980
+ sunrise: outline(Sunrise),
981
+ sunset: outline(Sunset),
982
+ 'cloud.drizzle': outline(CloudDrizzle),
983
+ 'cloud.fog': outline(CloudFog),
984
+ 'cloud.heavyrain': outline(CloudRainWind),
985
+ 'cloud.hail': outline(CloudHail),
986
+ tornado: outline(Tornado),
987
+ humidity: outline(Droplets),
988
+ pawprint: outline(PawPrint),
989
+ hare: outline(Rabbit),
990
+ tortoise: outline(Turtle),
991
+ fish: outline(Fish),
992
+ bird: outline(Bird),
993
+ // The Swift logo: a bird in flight.
994
+ swift: outline(Bird),
995
+ cat: outline(Cat),
996
+ dog: outline(Dog),
997
+ carrot: outline(Carrot),
998
+ 'cup.and.saucer': outline(Coffee),
999
+ mug: outline(Coffee),
1000
+ wineglass: outline(Wine),
1001
+ 'birthday.cake': outline(Cake),
1002
+ film: outline(Film),
1003
+ 'film.stack': outline(Clapperboard),
1004
+ 'camera.rotate': outline(SwitchCamera),
1005
+ 'photo.stack': outline(Images),
1006
+ 'photo.on.rectangle.angled': outline(Images),
1007
+ 'music.note.list': outline(ListMusic),
1008
+ 'music.mic': outline(MicVocal),
1009
+ radio: outline(Radio),
1010
+ airplayaudio: outline(Airplay),
1011
+ airplayvideo: outline(Airplay),
1012
+ 'play.rectangle': outline(MonitorPlay),
1013
+ 'play.square': outline(SquarePlay),
1014
+ icloud: outline(Cloud),
1015
+ 'icloud.fill': solid(Cloud),
1016
+ 'icloud.and.arrow.up': outline(CloudUpload),
1017
+ 'icloud.and.arrow.down': outline(CloudDownload),
1018
+ externaldrive: outline(HardDrive),
1019
+ 'server.rack': outline(Server),
1020
+ cylinder: outline(Database),
1021
+ network: outline(Network),
1022
+ faceid: outline(ScanFace),
1023
+ touchid: outline(Fingerprint),
1024
+ 'lock.shield': outline(ShieldCheck),
1025
+ 'hand.tap': outline(Pointer),
1026
+ 'hand.wave': outline(Hand),
1027
+ 'hand.thumbsdown': outline(ThumbsDown),
1028
+ 'hand.thumbsdown.fill': solid(ThumbsDown),
1029
+ eyeglasses: outline(Glasses),
1030
+ sparkle: outline(Sparkle),
1031
+ 'wand.and.stars': outline(WandSparkles),
1032
+ paintbrush: outline(Paintbrush),
1033
+ paintpalette: outline(Palette),
1034
+ 'pencil.tip': outline(PenTool),
1035
+ 'pencil.slash': outline(PencilOff),
1036
+ highlighter: outline(Highlighter),
1037
+ scissors: outline(Scissors),
1038
+ ruler: outline(Ruler),
1039
+ eyedropper: outline(Pipette),
1040
+ lasso: outline(Lasso),
1041
+ crop: outline(Crop),
1042
+ gauge: outline(Gauge),
1043
+ speedometer: outline(Gauge),
1044
+ hammer: outline(Hammer),
1045
+ puzzlepiece: outline(Puzzle),
1046
+ dice: outline(Dices),
1047
+ newspaper: outline(Newspaper),
1048
+ 'books.vertical': outline(Library),
1049
+ 'books.vertical.fill': solid(BOOKS_VERTICAL_FILL),
1050
+ 'note.text': outline(StickyNote),
1051
+ note: outline(StickyNote),
1052
+ 'location.circle': outline(LocateFixed),
1053
+ 'location.north': outline(Navigation2),
1054
+ 'location.slash': outline(MapPinOff),
1055
+ 'signpost.right': outline(Signpost),
1056
+ 'globe.americas': outline(Earth),
1057
+ 'globe.europe.africa': outline(Earth),
1058
+ 'bolt.slash': outline(ZapOff),
1059
+ 'battery.25': outline(BatteryLow),
1060
+ 'battery.50': outline(BatteryMedium),
1061
+ 'battery.75': outline(BatteryFull),
1062
+ 'battery.0': outline(BatteryWarning),
1063
+ 'wifi.slash': outline(WifiOff),
1064
+ cellularbars: outline(Signal),
1065
+ infinity: outline(Infinity),
1066
+ sum: outline(Sigma),
1067
+ command: outline(Command),
1068
+ option: outline(Option),
1069
+ 'delete.left': outline(Delete),
1070
+ return: outline(CornerDownLeft),
1071
+ 'xmark.bin': outline(Trash2),
1072
+ 'square.and.arrow.up.on.square': outline(Share2),
1073
+ 'link.badge.plus': outline(Link2),
1074
+ 'shippingbox.circle': outline(Package),
1075
+ 'graduationcap.fill': solid(GraduationCap),
1076
+ medal: outline(Medal),
1077
+ rosette: outline(Award),
1078
+ 'gift.circle': outline(Gift),
1079
+ studentdesk: outline(School),
552
1080
  };
553
1081
 
554
- /** Number of SF Symbol names the renderer can draw. */
1082
+ /** Number of SF Symbol names the map lists (more resolve as variants, see `lookupSymbol`). */
555
1083
  export const SF_SYMBOL_COUNT = Object.keys(SF_SYMBOLS).length;
556
1084
 
557
1085
  export function lookupSymbol(systemName: string): SymbolGlyph | undefined {
558
- return SF_SYMBOLS[systemName];
1086
+ const exact = SF_SYMBOLS[systemName];
1087
+ if (exact) return exact;
1088
+ if (!(systemName in variants)) variants[systemName] = resolveVariant(systemName) ?? null;
1089
+ return variants[systemName] ?? undefined;
1090
+ }
1091
+
1092
+ /** Resolved variants (null: none); an object because lucide's `Map` icon shadows the global. */
1093
+ const variants: Record<string, SymbolGlyph | null> = Object.create(null);
1094
+
1095
+ /** Shapes that alone would stand in for too much (`circle.grid.2x2` is not a circle). */
1096
+ const GENERIC_SHAPES = new Set(['circle', 'square', 'rectangle', 'app', 'capsule', 'triangle', 'diamond', 'hexagon', 'octagon', 'seal', 'shield']);
1097
+
1098
+ /**
1099
+ * A name the map does not list is drawn as the closest listed one: SF Symbol
1100
+ * names extend a base with modifiers, so `person.crop.circle.badge.xmark`
1101
+ * is drawn as `person.crop.circle`, `trash.circle.fill` as `trash.fill` and
1102
+ * `speaker.wave.2.fill` as `speaker.wave.2`. A `.badge…` suffix is dropped,
1103
+ * then trailing parts one at a time; a name with `.fill` tries every filled
1104
+ * form first. A `.slash` variant is never drawn as its base, which would
1105
+ * mean the opposite (`bell.slash` is "notifications off"); nor is a name
1106
+ * drawn as a bare shape. Unresolved names get the caller's placeholder.
1107
+ */
1108
+ function resolveVariant(systemName: string): SymbolGlyph | undefined {
1109
+ let parts = systemName.split('.');
1110
+ if (parts.includes('slash')) return undefined;
1111
+ const fill = parts.includes('fill');
1112
+ parts = parts.filter((part) => part !== 'fill');
1113
+ const badge = parts.indexOf('badge');
1114
+ if (badge > 0) parts = parts.slice(0, badge);
1115
+ // `trash.circle`, `arrow.down.circle.fill`, `heart.square.fill`: the base
1116
+ // glyph inside the enclosure (drawn whole, or filled with the glyph
1117
+ // knocked out), unless the table lists the full name's base form.
1118
+ const enclosure = parts.at(-1);
1119
+ if ((enclosure === 'circle' || enclosure === 'square') && parts.length > 1) {
1120
+ const listed = SF_SYMBOLS[parts.join('.')];
1121
+ if (listed) {
1122
+ // The listed outline form, its enclosure filled (`arrow.down.circle.fill`).
1123
+ if (fill && enclosingShapeIndex(listed.icon) >= 0) return { icon: listed.icon, fill: 'knockout' };
1124
+ } else {
1125
+ const base = parts.slice(0, -1).join('.');
1126
+ const glyph = GENERIC_SHAPES.has(base) ? undefined : lookupSymbol(base);
1127
+ if (glyph) return enclosed(glyph.icon, enclosure, fill);
1128
+ }
1129
+ }
1130
+ const bases: string[] = [];
1131
+ for (let length = parts.length; length > 0; length--) {
1132
+ const base = parts.slice(0, length).join('.');
1133
+ if (length === 1 && parts.length > 1 && GENERIC_SHAPES.has(base)) break;
1134
+ bases.push(base);
1135
+ }
1136
+ // A filled form at any length before an outline: `heart.circle.fill` is
1137
+ // closer to `heart.fill` than to `heart.circle`.
1138
+ const candidates = fill ? [...bases.map((base) => `${base}.fill`), ...bases] : bases;
1139
+ for (const name of candidates) {
1140
+ const glyph = SF_SYMBOLS[name];
1141
+ if (glyph) return glyph;
1142
+ }
1143
+ return undefined;
1144
+ }
1145
+
1146
+ /**
1147
+ * `icon` at 55% inside a circle or rounded square: outlined (`X.circle`), or
1148
+ * filled with the glyph knocked out of it (`X.circle.fill`), as SF Symbols
1149
+ * draw their enclosed forms.
1150
+ */
1151
+ function enclosed(icon: IconNode, enclosure: 'circle' | 'square', fill: boolean): SymbolGlyph {
1152
+ const outer: IconNode[number] =
1153
+ enclosure === 'circle' ? ['circle', { cx: '12', cy: '12', r: '10' }] : ['rect', { x: '3', y: '3', width: '18', height: '18', rx: '4' }];
1154
+ // Scaled about the center; the stroke is widened to keep its weight.
1155
+ const inner = icon.map(([tag, attrs]) => [tag, { ...attrs, transform: 'translate(5.4 5.4) scale(0.55)', 'stroke-width': '3.3' }] as IconNode[number]);
1156
+ return { icon: [outer, ...inner], fill: fill ? 'knockout' : 'outline' };
1157
+ }
1158
+
1159
+ /**
1160
+ * The name of the filled form of `systemName` when the table can draw one
1161
+ * (`house` → `house.fill`), else the name itself: a tab bar shows its items'
1162
+ * symbols filled, as iOS does (`symbolVariants` `.fill`).
1163
+ */
1164
+ export function filledSymbolName(systemName: string): string {
1165
+ if (systemName.split('.').includes('fill')) return systemName;
1166
+ const filled = `${systemName}.fill`;
1167
+ const glyph = lookupSymbol(filled);
1168
+ return glyph && glyph.fill !== 'outline' ? filled : systemName;
559
1169
  }
560
1170
 
561
1171
  const SVG_NS = 'http://www.w3.org/2000/svg';
562
1172
 
563
- /** Color used for inner marks of `knockout` glyphs; styles.css sets the variable. */
564
- const KNOCKOUT_COLOR = 'var(--sb-icon-knockout, var(--sb-color-system-background, #fff))';
1173
+ /**
1174
+ * The shape a `knockout` glyph fills: the enclosing circle or rectangle
1175
+ * (lucide lists it first in most glyphs but not all: `CirclePlay` starts
1176
+ * with its triangle), else the first shape.
1177
+ */
1178
+ export function outerShapeIndex(icon: IconNode): number {
1179
+ const index = enclosingShapeIndex(icon);
1180
+ return index >= 0 ? index : 0;
1181
+ }
1182
+
1183
+ /** The index of the circle or rectangle enclosing `icon`'s other marks, or -1. */
1184
+ function enclosingShapeIndex(icon: IconNode): number {
1185
+ return icon.findIndex(([tag, attrs]) => {
1186
+ const n = (key: string): number => Number(attrs[key] ?? 0);
1187
+ return (tag === 'circle' && n('r') >= 9) || (tag === 'rect' && n('width') >= 16 && n('height') >= 12);
1188
+ });
1189
+ }
1190
+
565
1191
 
566
1192
  /**
567
1193
  * Build the `<svg>` for a symbol. Returns `null` for unknown names; the
@@ -584,12 +1210,16 @@ export function renderSymbol(systemName: string): SVGElement | null {
584
1210
  shape.setAttribute('fill', 'currentColor');
585
1211
  }
586
1212
  } else if (glyph.fill === 'knockout') {
587
- const [outer, ...inner] = Array.from(svg.children);
588
- outer?.setAttribute('fill', 'currentColor');
589
- for (const mark of inner) {
590
- mark.setAttribute('stroke', KNOCKOUT_COLOR);
591
- mark.setAttribute('fill', 'none');
592
- }
1213
+ const shapes = Array.from(svg.children);
1214
+ const outer = outerShapeIndex(glyph.icon);
1215
+ shapes.forEach((shape, index) => {
1216
+ if (index === outer) {
1217
+ shape.setAttribute('fill', 'currentColor');
1218
+ } else {
1219
+ shape.setAttribute('stroke', KNOCKOUT_COLOR);
1220
+ shape.setAttribute('fill', 'none');
1221
+ }
1222
+ });
593
1223
  }
594
1224
  return svg;
595
1225
  }