@saschabrunnerch/arcgis-maps-sdk-js-ai-context 0.0.1

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 (49) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +201 -0
  3. package/bin/cli.js +173 -0
  4. package/contexts/4.34/claude/arcgis-3d-advanced/SKILL.md +586 -0
  5. package/contexts/4.34/claude/arcgis-advanced-layers/SKILL.md +431 -0
  6. package/contexts/4.34/claude/arcgis-analysis-services/SKILL.md +607 -0
  7. package/contexts/4.34/claude/arcgis-arcade/SKILL.md +366 -0
  8. package/contexts/4.34/claude/arcgis-authentication/SKILL.md +301 -0
  9. package/contexts/4.34/claude/arcgis-cim-symbols/SKILL.md +486 -0
  10. package/contexts/4.34/claude/arcgis-coordinates-projection/SKILL.md +406 -0
  11. package/contexts/4.34/claude/arcgis-core-maps/SKILL.md +739 -0
  12. package/contexts/4.34/claude/arcgis-core-utilities/SKILL.md +732 -0
  13. package/contexts/4.34/claude/arcgis-custom-rendering/SKILL.md +445 -0
  14. package/contexts/4.34/claude/arcgis-editing-advanced/SKILL.md +702 -0
  15. package/contexts/4.34/claude/arcgis-feature-effects/SKILL.md +393 -0
  16. package/contexts/4.34/claude/arcgis-geometry-operations/SKILL.md +489 -0
  17. package/contexts/4.34/claude/arcgis-imagery/SKILL.md +307 -0
  18. package/contexts/4.34/claude/arcgis-interaction/SKILL.md +572 -0
  19. package/contexts/4.34/claude/arcgis-knowledge-graphs/SKILL.md +582 -0
  20. package/contexts/4.34/claude/arcgis-layers/SKILL.md +601 -0
  21. package/contexts/4.34/claude/arcgis-map-tools/SKILL.md +668 -0
  22. package/contexts/4.34/claude/arcgis-media-layers/SKILL.md +290 -0
  23. package/contexts/4.34/claude/arcgis-popup-templates/SKILL.md +891 -0
  24. package/contexts/4.34/claude/arcgis-portal-content/SKILL.md +679 -0
  25. package/contexts/4.34/claude/arcgis-scene-effects/SKILL.md +512 -0
  26. package/contexts/4.34/claude/arcgis-smart-mapping/SKILL.md +686 -0
  27. package/contexts/4.34/claude/arcgis-tables-forms/SKILL.md +877 -0
  28. package/contexts/4.34/claude/arcgis-time-animation/SKILL.md +722 -0
  29. package/contexts/4.34/claude/arcgis-utility-networks/SKILL.md +301 -0
  30. package/contexts/4.34/claude/arcgis-visualization/SKILL.md +580 -0
  31. package/contexts/4.34/claude/arcgis-widgets-ui/SKILL.md +574 -0
  32. package/contexts/4.34/copilot/arcgis-3d.instructions.md +267 -0
  33. package/contexts/4.34/copilot/arcgis-analysis.instructions.md +294 -0
  34. package/contexts/4.34/copilot/arcgis-arcade.instructions.md +234 -0
  35. package/contexts/4.34/copilot/arcgis-authentication.instructions.md +187 -0
  36. package/contexts/4.34/copilot/arcgis-cim-symbols.instructions.md +177 -0
  37. package/contexts/4.34/copilot/arcgis-core-maps.instructions.md +246 -0
  38. package/contexts/4.34/copilot/arcgis-core-utilities.instructions.md +247 -0
  39. package/contexts/4.34/copilot/arcgis-editing.instructions.md +262 -0
  40. package/contexts/4.34/copilot/arcgis-geometry.instructions.md +225 -0
  41. package/contexts/4.34/copilot/arcgis-layers.instructions.md +278 -0
  42. package/contexts/4.34/copilot/arcgis-popup-templates.instructions.md +266 -0
  43. package/contexts/4.34/copilot/arcgis-portal-advanced.instructions.md +275 -0
  44. package/contexts/4.34/copilot/arcgis-smart-mapping.instructions.md +184 -0
  45. package/contexts/4.34/copilot/arcgis-time-animation.instructions.md +112 -0
  46. package/contexts/4.34/copilot/arcgis-visualization.instructions.md +321 -0
  47. package/contexts/4.34/copilot/arcgis-widgets-ui.instructions.md +277 -0
  48. package/lib/installer.js +379 -0
  49. package/package.json +45 -0
@@ -0,0 +1,891 @@
1
+ ---
2
+ name: arcgis-popup-templates
3
+ description: Configure rich popup content with text, fields, media, charts, attachments, and related records. Use for customizing feature popups with dynamic content, expressions, and interactive elements.
4
+ ---
5
+
6
+ # ArcGIS Popup Templates
7
+
8
+ Use this skill for creating and customizing popup templates with various content types.
9
+
10
+ ## PopupTemplate Overview
11
+
12
+ | Content Type | Purpose |
13
+ |--------------|---------|
14
+ | TextContent | HTML or plain text |
15
+ | FieldsContent | Attribute table |
16
+ | MediaContent | Charts and images |
17
+ | AttachmentsContent | File attachments |
18
+ | ExpressionContent | Arcade expression results |
19
+ | CustomContent | Custom HTML/JavaScript |
20
+ | RelationshipContent | Related records |
21
+
22
+ ## Basic PopupTemplate
23
+
24
+ ```javascript
25
+ layer.popupTemplate = {
26
+ title: "{name}",
27
+ content: "Population: {population}<br>Area: {area} sq mi"
28
+ };
29
+ ```
30
+
31
+ ### With Field Substitution
32
+
33
+ ```javascript
34
+ layer.popupTemplate = {
35
+ title: "{city_name}, {state}",
36
+ content: `
37
+ <h3>Demographics</h3>
38
+ <p>Population: {population:NumberFormat(places: 0)}</p>
39
+ <p>Median Income: {median_income:NumberFormat(digitSeparator: true, places: 0)}</p>
40
+ <p>Founded: {founded_date:DateFormat(selector: 'date', datePattern: 'MMMM d, yyyy')}</p>
41
+ `
42
+ };
43
+ ```
44
+
45
+ ## Content Array (Multiple Content Types)
46
+
47
+ ```javascript
48
+ layer.popupTemplate = {
49
+ title: "{name}",
50
+ content: [
51
+ {
52
+ type: "text",
53
+ text: "<b>Overview</b><br>{description}"
54
+ },
55
+ {
56
+ type: "fields",
57
+ fieldInfos: [
58
+ { fieldName: "population", label: "Population" },
59
+ { fieldName: "area", label: "Area (sq mi)" }
60
+ ]
61
+ },
62
+ {
63
+ type: "media",
64
+ mediaInfos: [{
65
+ type: "pie-chart",
66
+ title: "Demographics",
67
+ value: {
68
+ fields: ["white", "black", "asian", "other"]
69
+ }
70
+ }]
71
+ }
72
+ ]
73
+ };
74
+ ```
75
+
76
+ ## Content Types
77
+
78
+ ### TextContent
79
+
80
+ Display HTML or text.
81
+
82
+ ```javascript
83
+ {
84
+ type: "text",
85
+ text: `
86
+ <div style="padding: 10px;">
87
+ <h2>{name}</h2>
88
+ <p>{description}</p>
89
+ <a href="{website}" target="_blank">Visit Website</a>
90
+ </div>
91
+ `
92
+ }
93
+ ```
94
+
95
+ ### FieldsContent
96
+
97
+ Display attributes as a table.
98
+
99
+ ```javascript
100
+ {
101
+ type: "fields",
102
+ fieldInfos: [
103
+ {
104
+ fieldName: "name",
105
+ label: "Name"
106
+ },
107
+ {
108
+ fieldName: "population",
109
+ label: "Population",
110
+ format: {
111
+ digitSeparator: true,
112
+ places: 0
113
+ }
114
+ },
115
+ {
116
+ fieldName: "date_created",
117
+ label: "Created",
118
+ format: {
119
+ dateFormat: "short-date"
120
+ }
121
+ },
122
+ {
123
+ fieldName: "percentage",
124
+ label: "Percentage",
125
+ format: {
126
+ places: 2,
127
+ digitSeparator: true
128
+ }
129
+ }
130
+ ]
131
+ }
132
+ ```
133
+
134
+ #### Date Formats
135
+
136
+ - `short-date` - 12/30/2024
137
+ - `short-date-short-time` - 12/30/2024, 3:30 PM
138
+ - `short-date-short-time-24` - 12/30/2024, 15:30
139
+ - `short-date-long-time` - 12/30/2024, 3:30:45 PM
140
+ - `short-date-long-time-24` - 12/30/2024, 15:30:45
141
+ - `long-month-day-year` - December 30, 2024
142
+ - `long-month-day-year-short-time` - December 30, 2024, 3:30 PM
143
+ - `long-month-day-year-long-time` - December 30, 2024, 3:30:45 PM
144
+ - `day-short-month-year` - 30 Dec 2024
145
+ - `year` - 2024
146
+
147
+ ### MediaContent
148
+
149
+ Display charts or images.
150
+
151
+ ```javascript
152
+ {
153
+ type: "media",
154
+ mediaInfos: [
155
+ {
156
+ title: "Sales by Quarter",
157
+ type: "column-chart", // bar-chart, pie-chart, line-chart, column-chart, image
158
+ value: {
159
+ fields: ["q1_sales", "q2_sales", "q3_sales", "q4_sales"],
160
+ normalizeField: "total_sales" // Optional
161
+ }
162
+ }
163
+ ]
164
+ }
165
+ ```
166
+
167
+ #### Chart Types
168
+
169
+ **Bar Chart**
170
+ ```javascript
171
+ {
172
+ type: "bar-chart",
173
+ title: "Population by Age",
174
+ value: {
175
+ fields: ["age_0_17", "age_18_34", "age_35_54", "age_55_plus"]
176
+ }
177
+ }
178
+ ```
179
+
180
+ **Pie Chart**
181
+ ```javascript
182
+ {
183
+ type: "pie-chart",
184
+ title: "Land Use Distribution",
185
+ value: {
186
+ fields: ["residential", "commercial", "industrial", "agricultural"]
187
+ }
188
+ }
189
+ ```
190
+
191
+ **Line Chart**
192
+ ```javascript
193
+ {
194
+ type: "line-chart",
195
+ title: "Monthly Sales",
196
+ value: {
197
+ fields: ["jan", "feb", "mar", "apr", "may", "jun", "jul", "aug", "sep", "oct", "nov", "dec"]
198
+ }
199
+ }
200
+ ```
201
+
202
+ **Column Chart**
203
+ ```javascript
204
+ {
205
+ type: "column-chart",
206
+ title: "Revenue vs Expenses",
207
+ value: {
208
+ fields: ["revenue", "expenses"]
209
+ }
210
+ }
211
+ ```
212
+
213
+ **Image**
214
+ ```javascript
215
+ {
216
+ type: "image",
217
+ title: "Property Photo",
218
+ value: {
219
+ sourceURL: "{image_url}",
220
+ linkURL: "{detail_page_url}"
221
+ }
222
+ }
223
+
224
+ // Fixed image
225
+ {
226
+ type: "image",
227
+ value: {
228
+ sourceURL: "https://example.com/logo.png"
229
+ }
230
+ }
231
+ ```
232
+
233
+ ### AttachmentsContent
234
+
235
+ Display file attachments.
236
+
237
+ ```javascript
238
+ {
239
+ type: "attachments",
240
+ displayType: "preview", // preview, list, auto
241
+ title: "Photos"
242
+ }
243
+
244
+ // With custom display
245
+ {
246
+ type: "attachments",
247
+ displayType: "list",
248
+ title: "Documents",
249
+ description: "Related files for this record"
250
+ }
251
+ ```
252
+
253
+ ### ExpressionContent
254
+
255
+ Display Arcade expression results.
256
+
257
+ ```javascript
258
+ // First, define expression in expressionInfos
259
+ layer.popupTemplate = {
260
+ expressionInfos: [
261
+ {
262
+ name: "population-density",
263
+ title: "Population Density",
264
+ expression: "Round($feature.population / $feature.area, 2)"
265
+ },
266
+ {
267
+ name: "age-category",
268
+ title: "Age Category",
269
+ expression: `
270
+ var age = $feature.building_age;
271
+ if (age < 25) return "New";
272
+ if (age < 50) return "Moderate";
273
+ return "Historic";
274
+ `
275
+ }
276
+ ],
277
+ content: [
278
+ {
279
+ type: "expression",
280
+ expressionInfo: {
281
+ name: "population-density"
282
+ }
283
+ }
284
+ ]
285
+ };
286
+ ```
287
+
288
+ ### CustomContent
289
+
290
+ Create fully custom content with JavaScript.
291
+
292
+ ```javascript
293
+ import CustomContent from "@arcgis/core/popup/content/CustomContent.js";
294
+
295
+ const customContent = new CustomContent({
296
+ outFields: ["*"],
297
+ creator: (event) => {
298
+ const div = document.createElement("div");
299
+ const graphic = event.graphic;
300
+
301
+ div.innerHTML = `
302
+ <div class="custom-popup">
303
+ <h3>${graphic.attributes.name}</h3>
304
+ <canvas id="chart-${graphic.attributes.OBJECTID}"></canvas>
305
+ </div>
306
+ `;
307
+
308
+ // Add custom logic, charts, etc.
309
+ return div;
310
+ }
311
+ });
312
+
313
+ layer.popupTemplate = {
314
+ title: "{name}",
315
+ content: [customContent]
316
+ };
317
+ ```
318
+
319
+ ### RelationshipContent
320
+
321
+ Display related records.
322
+
323
+ ```javascript
324
+ {
325
+ type: "relationship",
326
+ relationshipId: 0, // Relationship ID from the layer
327
+ title: "Related Inspections",
328
+ displayCount: 5,
329
+ orderByFields: [
330
+ {
331
+ field: "inspection_date",
332
+ order: "desc"
333
+ }
334
+ ]
335
+ }
336
+ ```
337
+
338
+ ## FieldInfo Configuration
339
+
340
+ ### Basic FieldInfo
341
+
342
+ ```javascript
343
+ const fieldInfo = {
344
+ fieldName: "population",
345
+ label: "Population",
346
+ visible: true,
347
+ isEditable: false,
348
+ statisticType: "sum" // For clustering: sum, min, max, avg, count
349
+ };
350
+ ```
351
+
352
+ ### Number Formatting
353
+
354
+ ```javascript
355
+ {
356
+ fieldName: "revenue",
357
+ label: "Annual Revenue",
358
+ format: {
359
+ digitSeparator: true,
360
+ places: 2
361
+ }
362
+ }
363
+
364
+ // Currency (manual)
365
+ {
366
+ fieldName: "price",
367
+ label: "Price",
368
+ format: {
369
+ digitSeparator: true,
370
+ places: 2
371
+ }
372
+ }
373
+ // Use expression for currency symbol
374
+ ```
375
+
376
+ ### Date Formatting
377
+
378
+ ```javascript
379
+ {
380
+ fieldName: "created_date",
381
+ label: "Created",
382
+ format: {
383
+ dateFormat: "long-month-day-year"
384
+ }
385
+ }
386
+ ```
387
+
388
+ ### Tooltip
389
+
390
+ ```javascript
391
+ {
392
+ fieldName: "status",
393
+ label: "Status",
394
+ tooltip: "Current processing status of the request"
395
+ }
396
+ ```
397
+
398
+ ## RelatedRecordsInfo
399
+
400
+ Configure how related records are displayed.
401
+
402
+ ```javascript
403
+ layer.popupTemplate = {
404
+ title: "{name}",
405
+ content: [{
406
+ type: "relationship",
407
+ relationshipId: 0
408
+ }],
409
+ relatedRecordsInfo: {
410
+ showRelatedRecords: true,
411
+ orderByFields: [{
412
+ field: "date",
413
+ order: "desc"
414
+ }]
415
+ }
416
+ };
417
+ ```
418
+
419
+ ## Actions
420
+
421
+ Add custom buttons to popups.
422
+
423
+ ```javascript
424
+ layer.popupTemplate = {
425
+ title: "{name}",
426
+ content: "...",
427
+ actions: [
428
+ {
429
+ id: "zoom-to",
430
+ title: "Zoom To",
431
+ className: "esri-icon-zoom-in-magnifying-glass"
432
+ },
433
+ {
434
+ id: "edit",
435
+ title: "Edit",
436
+ className: "esri-icon-edit"
437
+ },
438
+ {
439
+ id: "delete",
440
+ title: "Delete",
441
+ className: "esri-icon-trash"
442
+ }
443
+ ]
444
+ };
445
+
446
+ // Handle action clicks
447
+ view.popup.on("trigger-action", (event) => {
448
+ if (event.action.id === "zoom-to") {
449
+ view.goTo(view.popup.selectedFeature);
450
+ } else if (event.action.id === "edit") {
451
+ // Open editor
452
+ startEditing(view.popup.selectedFeature);
453
+ } else if (event.action.id === "delete") {
454
+ // Delete feature
455
+ deleteFeature(view.popup.selectedFeature);
456
+ }
457
+ });
458
+ ```
459
+
460
+ ### Action Button Types
461
+
462
+ ```javascript
463
+ // Icon button
464
+ {
465
+ id: "info",
466
+ title: "More Info",
467
+ className: "esri-icon-description"
468
+ }
469
+
470
+ // Text button
471
+ {
472
+ id: "report",
473
+ title: "Generate Report",
474
+ type: "button"
475
+ }
476
+
477
+ // Toggle button
478
+ {
479
+ id: "highlight",
480
+ title: "Highlight",
481
+ type: "toggle",
482
+ value: false
483
+ }
484
+ ```
485
+
486
+ ## Dynamic Content with Functions
487
+
488
+ ### Content as Function
489
+
490
+ ```javascript
491
+ layer.popupTemplate = {
492
+ title: "{name}",
493
+ content: (feature) => {
494
+ const attributes = feature.graphic.attributes;
495
+
496
+ // Conditional content
497
+ if (attributes.type === "residential") {
498
+ return `
499
+ <h3>Residential Property</h3>
500
+ <p>Bedrooms: ${attributes.bedrooms}</p>
501
+ <p>Bathrooms: ${attributes.bathrooms}</p>
502
+ `;
503
+ } else {
504
+ return `
505
+ <h3>Commercial Property</h3>
506
+ <p>Square Footage: ${attributes.sqft}</p>
507
+ <p>Zoning: ${attributes.zoning}</p>
508
+ `;
509
+ }
510
+ }
511
+ };
512
+ ```
513
+
514
+ ### Async Content Function
515
+
516
+ ```javascript
517
+ layer.popupTemplate = {
518
+ title: "{name}",
519
+ content: async (feature) => {
520
+ const id = feature.graphic.attributes.OBJECTID;
521
+
522
+ // Fetch additional data
523
+ const response = await fetch(`/api/details/${id}`);
524
+ const data = await response.json();
525
+
526
+ return `
527
+ <h3>${data.title}</h3>
528
+ <p>${data.description}</p>
529
+ <img src="${data.imageUrl}" />
530
+ `;
531
+ }
532
+ };
533
+ ```
534
+
535
+ ## Arcade Expressions
536
+
537
+ ### In Title
538
+
539
+ ```javascript
540
+ layer.popupTemplate = {
541
+ title: {
542
+ expression: `
543
+ var name = $feature.name;
544
+ var status = $feature.status;
545
+ return name + " (" + status + ")";
546
+ `
547
+ },
548
+ content: "..."
549
+ };
550
+ ```
551
+
552
+ ### Expression Infos
553
+
554
+ ```javascript
555
+ layer.popupTemplate = {
556
+ expressionInfos: [
557
+ {
558
+ name: "formatted-date",
559
+ title: "Formatted Date",
560
+ expression: `
561
+ var d = $feature.created_date;
562
+ return Text(d, "MMMM D, YYYY");
563
+ `
564
+ },
565
+ {
566
+ name: "calculated-field",
567
+ title: "Density",
568
+ expression: "Round($feature.population / AreaGeodetic($feature, 'square-miles'), 1)"
569
+ },
570
+ {
571
+ name: "conditional-value",
572
+ title: "Status",
573
+ expression: `
574
+ var val = $feature.score;
575
+ if (val >= 80) return "Excellent";
576
+ if (val >= 60) return "Good";
577
+ if (val >= 40) return "Fair";
578
+ return "Poor";
579
+ `
580
+ }
581
+ ],
582
+ content: [
583
+ {
584
+ type: "fields",
585
+ fieldInfos: [
586
+ { fieldName: "expression/formatted-date", label: "Created" },
587
+ { fieldName: "expression/calculated-field", label: "Population Density" },
588
+ { fieldName: "expression/conditional-value", label: "Rating" }
589
+ ]
590
+ }
591
+ ]
592
+ };
593
+ ```
594
+
595
+ ## OutFields
596
+
597
+ Specify which fields to retrieve.
598
+
599
+ ```javascript
600
+ layer.popupTemplate = {
601
+ title: "{name}",
602
+ content: "...",
603
+ outFields: ["name", "population", "area", "created_date"]
604
+ };
605
+
606
+ // All fields
607
+ layer.popupTemplate = {
608
+ title: "{name}",
609
+ content: "...",
610
+ outFields: ["*"]
611
+ };
612
+ ```
613
+
614
+ ## Last Edit Info
615
+
616
+ Show who last edited the feature.
617
+
618
+ ```javascript
619
+ layer.popupTemplate = {
620
+ title: "{name}",
621
+ content: "...",
622
+ lastEditInfoEnabled: true
623
+ };
624
+ ```
625
+
626
+ ## Popup Template from JSON
627
+
628
+ ```javascript
629
+ const popupTemplate = {
630
+ title: "{name}",
631
+ content: [{
632
+ type: "fields",
633
+ fieldInfos: [
634
+ { fieldName: "category", label: "Category" },
635
+ { fieldName: "value", label: "Value" }
636
+ ]
637
+ }],
638
+ outFields: ["name", "category", "value"]
639
+ };
640
+
641
+ // Apply to layer
642
+ layer.popupTemplate = popupTemplate;
643
+
644
+ // Or create from class
645
+ import PopupTemplate from "@arcgis/core/PopupTemplate.js";
646
+ layer.popupTemplate = new PopupTemplate(popupTemplate);
647
+ ```
648
+
649
+ ## Layer-Specific Popup
650
+
651
+ ### FeatureLayer with Popup
652
+
653
+ ```javascript
654
+ const featureLayer = new FeatureLayer({
655
+ url: "https://services.arcgis.com/.../FeatureServer/0",
656
+ popupTemplate: {
657
+ title: "{NAME}",
658
+ content: [{
659
+ type: "fields",
660
+ fieldInfos: [
661
+ { fieldName: "NAME", label: "Name" },
662
+ { fieldName: "POP2020", label: "Population (2020)", format: { digitSeparator: true } }
663
+ ]
664
+ }]
665
+ }
666
+ });
667
+ ```
668
+
669
+ ### GeoJSONLayer with Popup
670
+
671
+ ```javascript
672
+ const geoJsonLayer = new GeoJSONLayer({
673
+ url: "data.geojson",
674
+ popupTemplate: {
675
+ title: "{properties/name}", // Note: GeoJSON uses properties/fieldName
676
+ content: "{properties/description}"
677
+ }
678
+ });
679
+ ```
680
+
681
+ ### GraphicsLayer Popup
682
+
683
+ ```javascript
684
+ const graphic = new Graphic({
685
+ geometry: point,
686
+ attributes: {
687
+ name: "Location A",
688
+ value: 100
689
+ },
690
+ popupTemplate: {
691
+ title: "{name}",
692
+ content: "Value: {value}"
693
+ }
694
+ });
695
+
696
+ graphicsLayer.add(graphic);
697
+ ```
698
+
699
+ ## Clustering Popups
700
+
701
+ ```javascript
702
+ layer.featureReduction = {
703
+ type: "cluster",
704
+ clusterRadius: 80,
705
+ popupTemplate: {
706
+ title: "Cluster of {cluster_count} features",
707
+ content: [{
708
+ type: "fields",
709
+ fieldInfos: [
710
+ {
711
+ fieldName: "cluster_count",
712
+ label: "Features in cluster"
713
+ },
714
+ {
715
+ fieldName: "cluster_avg_population",
716
+ label: "Average Population",
717
+ format: { digitSeparator: true, places: 0 }
718
+ }
719
+ ]
720
+ }]
721
+ },
722
+ clusterMinSize: 16,
723
+ clusterMaxSize: 60,
724
+ fields: [{
725
+ name: "cluster_avg_population",
726
+ alias: "Average Population",
727
+ onStatisticField: "population",
728
+ statisticType: "avg"
729
+ }]
730
+ };
731
+ ```
732
+
733
+ ## Common Patterns
734
+
735
+ ### Popup with Image Gallery
736
+
737
+ ```javascript
738
+ layer.popupTemplate = {
739
+ title: "{name}",
740
+ content: [
741
+ {
742
+ type: "text",
743
+ text: "{description}"
744
+ },
745
+ {
746
+ type: "attachments",
747
+ displayType: "preview"
748
+ }
749
+ ]
750
+ };
751
+ ```
752
+
753
+ ### Multi-Tab Style Popup
754
+
755
+ ```javascript
756
+ layer.popupTemplate = {
757
+ title: "{name}",
758
+ content: [
759
+ {
760
+ type: "text",
761
+ text: "<b>General Information</b>"
762
+ },
763
+ {
764
+ type: "fields",
765
+ fieldInfos: [
766
+ { fieldName: "category", label: "Category" },
767
+ { fieldName: "status", label: "Status" }
768
+ ]
769
+ },
770
+ {
771
+ type: "text",
772
+ text: "<hr><b>Statistics</b>"
773
+ },
774
+ {
775
+ type: "media",
776
+ mediaInfos: [{
777
+ type: "pie-chart",
778
+ title: "Distribution",
779
+ value: { fields: ["typeA", "typeB", "typeC"] }
780
+ }]
781
+ }
782
+ ]
783
+ };
784
+ ```
785
+
786
+ ### Conditional Content
787
+
788
+ ```javascript
789
+ layer.popupTemplate = {
790
+ title: "{name}",
791
+ expressionInfos: [{
792
+ name: "show-warning",
793
+ expression: "$feature.risk_level > 7"
794
+ }],
795
+ content: (feature) => {
796
+ const content = [{
797
+ type: "fields",
798
+ fieldInfos: [
799
+ { fieldName: "name", label: "Name" },
800
+ { fieldName: "risk_level", label: "Risk Level" }
801
+ ]
802
+ }];
803
+
804
+ if (feature.graphic.attributes.risk_level > 7) {
805
+ content.unshift({
806
+ type: "text",
807
+ text: '<div style="background: #ffcccc; padding: 10px;">⚠️ High Risk Area</div>'
808
+ });
809
+ }
810
+
811
+ return content;
812
+ }
813
+ };
814
+ ```
815
+
816
+ ## TypeScript Usage
817
+
818
+ PopupTemplate content uses autocasting with `type` properties. For TypeScript safety, use `as const`:
819
+
820
+ ```typescript
821
+ // Use 'as const' for type safety
822
+ layer.popupTemplate = {
823
+ title: "{name}",
824
+ content: [
825
+ {
826
+ type: "fields",
827
+ fieldInfos: [
828
+ { fieldName: "name", label: "Name" },
829
+ { fieldName: "population", label: "Population" }
830
+ ]
831
+ },
832
+ {
833
+ type: "media",
834
+ mediaInfos: [{
835
+ type: "pie-chart",
836
+ value: { fields: ["typeA", "typeB"] }
837
+ }]
838
+ }
839
+ ]
840
+ } as const;
841
+ ```
842
+
843
+ > **Tip:** See [arcgis-core-maps skill](../arcgis-core-maps/SKILL.md) for detailed guidance on autocasting vs explicit classes.
844
+
845
+ ## Common Pitfalls
846
+
847
+ 1. **Field Names Case Sensitive**: Field names must match exactly
848
+ ```javascript
849
+ // If field is "Population" (capital P)
850
+ content: "{Population}" // Correct
851
+ content: "{population}" // Wrong - shows literal {population}
852
+ ```
853
+
854
+ 2. **OutFields Required**: Fields used in popup must be in outFields
855
+ ```javascript
856
+ popupTemplate: {
857
+ title: "{name}",
858
+ content: "{description}",
859
+ outFields: ["name", "description"] // Both required
860
+ }
861
+ ```
862
+
863
+ 3. **GeoJSON Field Path**: GeoJSON requires `properties/` prefix
864
+ ```javascript
865
+ // GeoJSON
866
+ title: "{properties/name}"
867
+ // Regular FeatureLayer
868
+ title: "{name}"
869
+ ```
870
+
871
+ 4. **Expression Reference**: Use `expression/` prefix for Arcade expressions
872
+ ```javascript
873
+ fieldInfos: [
874
+ { fieldName: "expression/my-expression", label: "Calculated" }
875
+ ]
876
+ ```
877
+
878
+ 5. **Async Content**: Function content must return a value or Promise
879
+ ```javascript
880
+ // Wrong - no return
881
+ content: (feature) => {
882
+ const div = document.createElement("div");
883
+ }
884
+
885
+ // Correct
886
+ content: (feature) => {
887
+ const div = document.createElement("div");
888
+ return div;
889
+ }
890
+ ```
891
+