@crockery/fellowship-components 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +126 -0
- package/RELEASING.md +75 -0
- package/THIRD_PARTY_LICENSES.md +79 -0
- package/dist/assets/ibm-plex-mono-latin-500-normal-DSY6xOcd.woff2 +0 -0
- package/dist/assets/ibm-plex-mono-latin-600-normal-BgSNZQsw.woff2 +0 -0
- package/dist/assets/ibm-plex-sans-condensed-latin-400-normal-DKyD6XsY.woff2 +0 -0
- package/dist/assets/ibm-plex-sans-condensed-latin-500-normal-BqaLkUoX.woff2 +0 -0
- package/dist/assets/ibm-plex-sans-condensed-latin-600-normal-CRd5VyFf.woff2 +0 -0
- package/dist/assets/ibm-plex-sans-condensed-latin-700-normal-D8r4s4aS.woff2 +0 -0
- package/dist/chunks/data-source-CoIFvV72.js +10 -0
- package/dist/chunks/data-source-CoIFvV72.js.map +1 -0
- package/dist/chunks/decorate-B_ZTN9li.js +92 -0
- package/dist/chunks/decorate-B_ZTN9li.js.map +1 -0
- package/dist/chunks/dungeon-map-IPN6sEDj.js +1144 -0
- package/dist/chunks/dungeon-map-IPN6sEDj.js.map +1 -0
- package/dist/chunks/talent-calculator-Ch1Pbbba.js +1086 -0
- package/dist/chunks/talent-calculator-Ch1Pbbba.js.map +1 -0
- package/dist/chunks/tooltip-DoQVSnOn.js +412 -0
- package/dist/chunks/tooltip-DoQVSnOn.js.map +1 -0
- package/dist/data/data-source.d.ts +17 -0
- package/dist/data/data-source.d.ts.map +1 -0
- package/dist/data/package-data-source.d.ts +6 -0
- package/dist/data/package-data-source.d.ts.map +1 -0
- package/dist/data/package-talent-data-source.d.ts +6 -0
- package/dist/data/package-talent-data-source.d.ts.map +1 -0
- package/dist/data/talent-data-source.d.ts +11 -0
- package/dist/data/talent-data-source.d.ts.map +1 -0
- package/dist/dungeon-map/dungeon-map.d.ts +56 -0
- package/dist/dungeon-map/dungeon-map.d.ts.map +1 -0
- package/dist/dungeon-map/dungeon-map.styles.d.ts +2 -0
- package/dist/dungeon-map/dungeon-map.styles.d.ts.map +1 -0
- package/dist/dungeon-map/index.d.ts +5 -0
- package/dist/dungeon-map/index.d.ts.map +1 -0
- package/dist/dungeon-map/layout.d.ts +3 -0
- package/dist/dungeon-map/layout.d.ts.map +1 -0
- package/dist/dungeon-map/render-model.d.ts +7 -0
- package/dist/dungeon-map/render-model.d.ts.map +1 -0
- package/dist/dungeon-map/types.d.ts +107 -0
- package/dist/dungeon-map/types.d.ts.map +1 -0
- package/dist/dungeon-map/viewport-controller.d.ts +13 -0
- package/dist/dungeon-map/viewport-controller.d.ts.map +1 -0
- package/dist/dungeon-map.js +7 -0
- package/dist/dungeon-map.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8 -0
- package/dist/react/dungeon-map.d.ts +22 -0
- package/dist/react/dungeon-map.d.ts.map +1 -0
- package/dist/react/dungeon-map.js +180 -0
- package/dist/react/dungeon-map.js.map +1 -0
- package/dist/react/index.d.ts +7 -0
- package/dist/react/index.d.ts.map +1 -0
- package/dist/react/talent-calculator.d.ts +22 -0
- package/dist/react/talent-calculator.d.ts.map +1 -0
- package/dist/react/talent-calculator.js +71 -0
- package/dist/react/talent-calculator.js.map +1 -0
- package/dist/react/tooltip.d.ts +15 -0
- package/dist/react/tooltip.d.ts.map +1 -0
- package/dist/react/tooltip.js +26 -0
- package/dist/react/tooltip.js.map +1 -0
- package/dist/react.js +4 -0
- package/dist/styles/field-manual-fonts.d.ts +3 -0
- package/dist/styles/field-manual-fonts.d.ts.map +1 -0
- package/dist/styles/field-manual.styles.d.ts +3 -0
- package/dist/styles/field-manual.styles.d.ts.map +1 -0
- package/dist/talent-calculator/index.d.ts +5 -0
- package/dist/talent-calculator/index.d.ts.map +1 -0
- package/dist/talent-calculator/render-model.d.ts +5 -0
- package/dist/talent-calculator/render-model.d.ts.map +1 -0
- package/dist/talent-calculator/selection-state.d.ts +10 -0
- package/dist/talent-calculator/selection-state.d.ts.map +1 -0
- package/dist/talent-calculator/talent-calculator.d.ts +51 -0
- package/dist/talent-calculator/talent-calculator.d.ts.map +1 -0
- package/dist/talent-calculator/talent-calculator.styles.d.ts +2 -0
- package/dist/talent-calculator/talent-calculator.styles.d.ts.map +1 -0
- package/dist/talent-calculator/types.d.ts +92 -0
- package/dist/talent-calculator/types.d.ts.map +1 -0
- package/dist/talent-calculator.js +7 -0
- package/dist/talent-calculator.js.map +1 -0
- package/dist/tooltip/index.d.ts +6 -0
- package/dist/tooltip/index.d.ts.map +1 -0
- package/dist/tooltip/positioning.d.ts +23 -0
- package/dist/tooltip/positioning.d.ts.map +1 -0
- package/dist/tooltip/rich-text.d.ts +4 -0
- package/dist/tooltip/rich-text.d.ts.map +1 -0
- package/dist/tooltip/tooltip.d.ts +33 -0
- package/dist/tooltip/tooltip.d.ts.map +1 -0
- package/dist/tooltip/tooltip.styles.d.ts +2 -0
- package/dist/tooltip/tooltip.styles.d.ts.map +1 -0
- package/dist/tooltip/types.d.ts +19 -0
- package/dist/tooltip/types.d.ts.map +1 -0
- package/dist/tooltip.js +7 -0
- package/dist/tooltip.js.map +1 -0
- package/package.json +149 -0
- package/src/dungeon-map/README.md +168 -0
- package/src/talent-calculator/README.md +190 -0
- package/src/tooltip/README.md +113 -0
package/package.json
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@crockery/fellowship-components",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Lit web components for rendering Fellowship game data.",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "git+https://github.com/Crockery/fellowship-components.git"
|
|
8
|
+
},
|
|
9
|
+
"bugs": {
|
|
10
|
+
"url": "https://github.com/Crockery/fellowship-components/issues"
|
|
11
|
+
},
|
|
12
|
+
"homepage": "https://github.com/Crockery/fellowship-components#readme",
|
|
13
|
+
"keywords": [
|
|
14
|
+
"fellowship",
|
|
15
|
+
"web-components",
|
|
16
|
+
"lit",
|
|
17
|
+
"react",
|
|
18
|
+
"dungeon-map",
|
|
19
|
+
"talent-calculator",
|
|
20
|
+
"tooltip"
|
|
21
|
+
],
|
|
22
|
+
"license": "MIT",
|
|
23
|
+
"type": "module",
|
|
24
|
+
"sideEffects": [
|
|
25
|
+
"./dist/index.js",
|
|
26
|
+
"./dist/dungeon-map.js",
|
|
27
|
+
"./dist/talent-calculator.js",
|
|
28
|
+
"./dist/tooltip.js",
|
|
29
|
+
"./dist/react.js",
|
|
30
|
+
"./dist/react/*.js"
|
|
31
|
+
],
|
|
32
|
+
"main": "./dist/index.js",
|
|
33
|
+
"module": "./dist/index.js",
|
|
34
|
+
"types": "./dist/index.d.ts",
|
|
35
|
+
"exports": {
|
|
36
|
+
".": {
|
|
37
|
+
"types": "./dist/index.d.ts",
|
|
38
|
+
"import": "./dist/index.js",
|
|
39
|
+
"default": "./dist/index.js"
|
|
40
|
+
},
|
|
41
|
+
"./dungeon-map": {
|
|
42
|
+
"types": "./dist/dungeon-map/index.d.ts",
|
|
43
|
+
"import": "./dist/dungeon-map.js",
|
|
44
|
+
"default": "./dist/dungeon-map.js"
|
|
45
|
+
},
|
|
46
|
+
"./talent-calculator": {
|
|
47
|
+
"types": "./dist/talent-calculator/index.d.ts",
|
|
48
|
+
"import": "./dist/talent-calculator.js",
|
|
49
|
+
"default": "./dist/talent-calculator.js"
|
|
50
|
+
},
|
|
51
|
+
"./tooltip": {
|
|
52
|
+
"types": "./dist/tooltip/index.d.ts",
|
|
53
|
+
"import": "./dist/tooltip.js",
|
|
54
|
+
"default": "./dist/tooltip.js"
|
|
55
|
+
},
|
|
56
|
+
"./react": {
|
|
57
|
+
"types": "./dist/react/index.d.ts",
|
|
58
|
+
"import": "./dist/react.js",
|
|
59
|
+
"default": "./dist/react.js"
|
|
60
|
+
},
|
|
61
|
+
"./react/dungeon-map": {
|
|
62
|
+
"types": "./dist/react/dungeon-map.d.ts",
|
|
63
|
+
"import": "./dist/react/dungeon-map.js",
|
|
64
|
+
"default": "./dist/react/dungeon-map.js"
|
|
65
|
+
},
|
|
66
|
+
"./react/talent-calculator": {
|
|
67
|
+
"types": "./dist/react/talent-calculator.d.ts",
|
|
68
|
+
"import": "./dist/react/talent-calculator.js",
|
|
69
|
+
"default": "./dist/react/talent-calculator.js"
|
|
70
|
+
},
|
|
71
|
+
"./react/tooltip": {
|
|
72
|
+
"types": "./dist/react/tooltip.d.ts",
|
|
73
|
+
"import": "./dist/react/tooltip.js",
|
|
74
|
+
"default": "./dist/react/tooltip.js"
|
|
75
|
+
},
|
|
76
|
+
"./package.json": "./package.json"
|
|
77
|
+
},
|
|
78
|
+
"files": [
|
|
79
|
+
"dist",
|
|
80
|
+
"src/*/README.md",
|
|
81
|
+
"README.md",
|
|
82
|
+
"RELEASING.md",
|
|
83
|
+
"LICENSE",
|
|
84
|
+
"THIRD_PARTY_LICENSES.md"
|
|
85
|
+
],
|
|
86
|
+
"publishConfig": {
|
|
87
|
+
"access": "public"
|
|
88
|
+
},
|
|
89
|
+
"engines": {
|
|
90
|
+
"node": "^20.19.0 || >=22.12.0"
|
|
91
|
+
},
|
|
92
|
+
"packageManager": "pnpm@11.3.0",
|
|
93
|
+
"scripts": {
|
|
94
|
+
"build": "vite build && tsc -p tsconfig.build.json",
|
|
95
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
96
|
+
"lint": "biome lint .",
|
|
97
|
+
"format": "biome format --write .",
|
|
98
|
+
"format:check": "biome format .",
|
|
99
|
+
"test": "pnpm test:unit && pnpm test:browser",
|
|
100
|
+
"test:unit": "vitest run --project unit",
|
|
101
|
+
"test:browser": "vitest run --project browser",
|
|
102
|
+
"test:browser:smoke": "FELLOWSHIP_TEST_BROWSERS=smoke vitest run --project browser -t '\\[smoke\\]'",
|
|
103
|
+
"test:browser:all": "FELLOWSHIP_TEST_BROWSERS=all vitest run --project browser",
|
|
104
|
+
"ladle": "ladle serve",
|
|
105
|
+
"ladle:build": "ladle build",
|
|
106
|
+
"package:lint": "publint --strict --pack npm && attw --pack . --profile esm-only",
|
|
107
|
+
"pack:check": "pnpm build && node scripts/check-build.mjs && pnpm package:lint && pnpm pack --dry-run",
|
|
108
|
+
"prepublishOnly": "pnpm build && node scripts/check-build.mjs && pnpm package:lint",
|
|
109
|
+
"changeset": "changeset",
|
|
110
|
+
"version-packages": "changeset version",
|
|
111
|
+
"release": "changeset publish"
|
|
112
|
+
},
|
|
113
|
+
"dependencies": {
|
|
114
|
+
"@crockery/fellowship-data": "^1.0.0",
|
|
115
|
+
"@lit/react": "^1.0.8",
|
|
116
|
+
"lit": "^3.3.3"
|
|
117
|
+
},
|
|
118
|
+
"peerDependencies": {
|
|
119
|
+
"react": ">=18 <20",
|
|
120
|
+
"react-dom": ">=18 <20"
|
|
121
|
+
},
|
|
122
|
+
"peerDependenciesMeta": {
|
|
123
|
+
"react": {
|
|
124
|
+
"optional": true
|
|
125
|
+
},
|
|
126
|
+
"react-dom": {
|
|
127
|
+
"optional": true
|
|
128
|
+
}
|
|
129
|
+
},
|
|
130
|
+
"devDependencies": {
|
|
131
|
+
"@biomejs/biome": "^2.5.3",
|
|
132
|
+
"@changesets/cli": "^2.31.0",
|
|
133
|
+
"@fontsource/ibm-plex-mono": "5.2.7",
|
|
134
|
+
"@fontsource/ibm-plex-sans-condensed": "5.2.8",
|
|
135
|
+
"@arethetypeswrong/cli": "^0.18.5",
|
|
136
|
+
"@ladle/react": "^5.1.1",
|
|
137
|
+
"@types/react": "^18.3.24",
|
|
138
|
+
"@types/react-dom": "^18.3.7",
|
|
139
|
+
"@vitejs/plugin-react-swc": "^4.3.1",
|
|
140
|
+
"@vitest/browser-playwright": "^4.1.10",
|
|
141
|
+
"playwright": "^1.61.1",
|
|
142
|
+
"publint": "^0.3.21",
|
|
143
|
+
"react": "^18.3.1",
|
|
144
|
+
"react-dom": "^18.3.1",
|
|
145
|
+
"typescript": "^5.9.3",
|
|
146
|
+
"vite": "^8.1.4",
|
|
147
|
+
"vitest": "^4.1.10"
|
|
148
|
+
}
|
|
149
|
+
}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
# Dungeon map
|
|
2
|
+
|
|
3
|
+
`<fellowship-dungeon-map>` draws a Fellowship dungeon map to canvas and places enemy and group
|
|
4
|
+
HTML in a synchronized, interactive DOM overlay.
|
|
5
|
+
|
|
6
|
+
## Import and basic usage
|
|
7
|
+
|
|
8
|
+
Importing the component entry point registers the custom element:
|
|
9
|
+
|
|
10
|
+
```ts
|
|
11
|
+
import "@crockery/fellowship-components/dungeon-map";
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
```html
|
|
15
|
+
<fellowship-dungeon-map
|
|
16
|
+
dungeon-id="Level.ID.DemonicRitual"
|
|
17
|
+
map-image-id="Level.ID.DemonicRitual:T_Map_Demonicritual_Start"
|
|
18
|
+
></fellowship-dungeon-map>
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
The default data source lazily loads dungeons, enemies, and the manifest from
|
|
22
|
+
`@crockery/fellowship-data`. When `mapImageId` is absent, the first upstream map image is
|
|
23
|
+
selected.
|
|
24
|
+
|
|
25
|
+
## Lit renderers
|
|
26
|
+
|
|
27
|
+
Renderer functions receive upstream enemy placement data plus normalized group geometry. Group
|
|
28
|
+
children are already position-aware and only need to be wrapped:
|
|
29
|
+
|
|
30
|
+
```ts
|
|
31
|
+
import { html } from "lit";
|
|
32
|
+
import type { FellowshipDungeonMapElement } from "@crockery/fellowship-components/dungeon-map";
|
|
33
|
+
|
|
34
|
+
const map = document.querySelector<FellowshipDungeonMapElement>("fellowship-dungeon-map")!;
|
|
35
|
+
|
|
36
|
+
map.renderEnemy = ({ model }) => html` <span>${model.enemy?.name ?? model.placement.id}</span> `;
|
|
37
|
+
|
|
38
|
+
map.renderGroup = ({ group, children }) => html`
|
|
39
|
+
<div class="pull" data-group=${group.id}>${children}</div>
|
|
40
|
+
`;
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
`LitEnemyRenderer` receives `{ model, group, dungeon, mapImage }`.
|
|
44
|
+
`LitGroupRenderer` receives `{ group, children, dungeon, mapImage }`, where `children`
|
|
45
|
+
contains positioned enemy anchors. Both return a Lit `TemplateResult` or `nothing`.
|
|
46
|
+
|
|
47
|
+
## React
|
|
48
|
+
|
|
49
|
+
```tsx
|
|
50
|
+
import { DungeonMapViewer } from "@crockery/fellowship-components/react/dungeon-map";
|
|
51
|
+
|
|
52
|
+
export function DungeonRoute() {
|
|
53
|
+
return (
|
|
54
|
+
<DungeonMapViewer
|
|
55
|
+
dungeonId="Level.ID.DemonicRitual"
|
|
56
|
+
renderEnemy={({ model }) => <EnemyMarker enemy={model.enemy} />}
|
|
57
|
+
renderGroup={({ group, children }) => <PullGroup group={group}>{children}</PullGroup>}
|
|
58
|
+
onMapImageChange={(event) => console.log(event.detail.mapImageId)}
|
|
59
|
+
/>
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Custom React nodes render through a React-owned light-DOM overlay while the Lit element retains
|
|
65
|
+
map loading, canvas drawing, positioning, and viewport interactions.
|
|
66
|
+
|
|
67
|
+
## Data and assets
|
|
68
|
+
|
|
69
|
+
Set `assetBaseUrl` to self-host package images:
|
|
70
|
+
|
|
71
|
+
```ts
|
|
72
|
+
map.assetBaseUrl = "https://static.example.com/fellowship-data/v1.0.0/";
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Replace `dataSource` for filtered, experimental, remote, or application-owned data:
|
|
76
|
+
|
|
77
|
+
```ts
|
|
78
|
+
import type { FellowshipDataSource } from "@crockery/fellowship-components/dungeon-map";
|
|
79
|
+
|
|
80
|
+
declare const applicationData: FellowshipDataSource;
|
|
81
|
+
|
|
82
|
+
const source: FellowshipDataSource = {
|
|
83
|
+
async loadDungeon(dungeonId, signal) {
|
|
84
|
+
return applicationData.loadDungeon(dungeonId, signal);
|
|
85
|
+
},
|
|
86
|
+
resolveAssetUrl(asset) {
|
|
87
|
+
return new URL(asset.path, "https://static.example.com/").href;
|
|
88
|
+
},
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
map.dataSource = source;
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## API
|
|
95
|
+
|
|
96
|
+
### Properties
|
|
97
|
+
|
|
98
|
+
- `dungeonId: string`
|
|
99
|
+
- `mapImageId: string | null`
|
|
100
|
+
- `assetBaseUrl: string | null` — `null` uses the package-pinned CDN.
|
|
101
|
+
- `dataSource: FellowshipDataSource | null` — `null` uses the package provider.
|
|
102
|
+
- `renderEnemy: LitEnemyRenderer | null`
|
|
103
|
+
- `renderGroup: LitGroupRenderer | null`
|
|
104
|
+
- `mapLabelFormatter: MapLabelFormatter | null`
|
|
105
|
+
- `showControls: boolean` — defaults to `true`.
|
|
106
|
+
- `showMapSelector: boolean` — defaults to `true`.
|
|
107
|
+
- `maxZoom: number` — defaults to `8`.
|
|
108
|
+
|
|
109
|
+
The React wrapper treats `mapImageId` as hybrid controlled state: an initial value selects the
|
|
110
|
+
starting floor, user selections update the rendered map immediately, and later prop changes still
|
|
111
|
+
replace the selection.
|
|
112
|
+
|
|
113
|
+
### Methods
|
|
114
|
+
|
|
115
|
+
- `fitToView()`
|
|
116
|
+
- `zoomIn()`
|
|
117
|
+
- `zoomOut()`
|
|
118
|
+
- `retry()`
|
|
119
|
+
|
|
120
|
+
### Events
|
|
121
|
+
|
|
122
|
+
- `fellowship-map-ready`
|
|
123
|
+
- `fellowship-map-error`
|
|
124
|
+
- `fellowship-map-warning`
|
|
125
|
+
- `fellowship-map-image-change`
|
|
126
|
+
- `fellowship-map-viewport-change`
|
|
127
|
+
|
|
128
|
+
All events bubble across the shadow boundary and carry typed `detail` payloads.
|
|
129
|
+
|
|
130
|
+
## Interaction and accessibility
|
|
131
|
+
|
|
132
|
+
- Mouse-wheel zoom stays anchored beneath the pointer.
|
|
133
|
+
- Pointer drag and arrow keys pan the map.
|
|
134
|
+
- Two touch pointers pinch to zoom.
|
|
135
|
+
- `+`, `-`, and `0` zoom in, zoom out, and fit.
|
|
136
|
+
- The map selector uses a tablist with arrow, Home, and End navigation.
|
|
137
|
+
- Initial load, map changes, and resize fit the map to the viewport.
|
|
138
|
+
- Reduced-motion preferences remove marker and control transitions.
|
|
139
|
+
|
|
140
|
+
## Theming
|
|
141
|
+
|
|
142
|
+
```css
|
|
143
|
+
fellowship-dungeon-map {
|
|
144
|
+
--fellowship-map-soot: #0b0f13;
|
|
145
|
+
--fellowship-map-ink: #f1eee5;
|
|
146
|
+
--fellowship-map-muted-ink: #b7b4aa;
|
|
147
|
+
--fellowship-map-border: rgb(241 238 229 / 10%);
|
|
148
|
+
--fellowship-map-focus: #a8e6ee;
|
|
149
|
+
--fellowship-map-rune: #69c4d4;
|
|
150
|
+
--fellowship-map-ember: #d9784c;
|
|
151
|
+
--fellowship-map-control-surface: rgb(21 28 35 / 92%);
|
|
152
|
+
--fellowship-map-enemy-size: 32px;
|
|
153
|
+
--fellowship-map-font-family: "Fellowship Field", sans-serif;
|
|
154
|
+
--fellowship-map-tactical-font-family: "Fellowship Tactical", monospace;
|
|
155
|
+
--fellowship-map-radius: 6px;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
fellowship-dungeon-map::part(control-button) {
|
|
159
|
+
font-weight: 800;
|
|
160
|
+
}
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
CSS parts: `viewport`, `map-canvas`, `overlay`, `group`, `enemy`, `controls`,
|
|
164
|
+
`control-button`, `map-selector`, `map-selector-button`, and `status`.
|
|
165
|
+
|
|
166
|
+
The map also inherits all shared `--fellowship-field-*` tokens. Component tokens above take
|
|
167
|
+
precedence. Compact control rails respond to the map element's container width, so narrow embedded
|
|
168
|
+
maps adapt even when the browser viewport is wide.
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
# Talent calculator
|
|
2
|
+
|
|
3
|
+
`<fellowship-talent-calculator>` renders a hero's six-row talent grid and manages an affordable
|
|
4
|
+
selection against a configurable point budget.
|
|
5
|
+
|
|
6
|
+
## Import and basic usage
|
|
7
|
+
|
|
8
|
+
Importing the entry point registers the calculator and its tooltip dependency:
|
|
9
|
+
|
|
10
|
+
```ts
|
|
11
|
+
import "@crockery/fellowship-components/talent-calculator";
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
```html
|
|
15
|
+
<fellowship-talent-calculator hero-id="firemage" talent-points="14"> </fellowship-talent-calculator>
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
The default provider lazily loads heroes, talents, and the manifest from
|
|
19
|
+
`@crockery/fellowship-data`. Missing or failed talent images use deterministic rune fallbacks.
|
|
20
|
+
|
|
21
|
+
## Point budget and selection
|
|
22
|
+
|
|
23
|
+
`talentPoints` is the total available budget, not the remaining point count. The header displays
|
|
24
|
+
`spent / available`.
|
|
25
|
+
|
|
26
|
+
- Any affordable combination may be selected, including multiple talents from one row.
|
|
27
|
+
- There are no tier prerequisites or one-per-row restrictions.
|
|
28
|
+
- Unaffordable talents remain focusable for tooltip and requirement discovery.
|
|
29
|
+
- Selected talents can always be removed.
|
|
30
|
+
- The **Clear All** button removes every selected talent without changing the point budget.
|
|
31
|
+
- Point controls normalize the total to an integer from `0` through `14`.
|
|
32
|
+
- The total cannot be decreased below currently spent points.
|
|
33
|
+
|
|
34
|
+
The element is hybrid controlled. User actions update `selectedTalentIds` and `talentPoints`
|
|
35
|
+
before events are emitted, and applications may set either property directly.
|
|
36
|
+
|
|
37
|
+
Externally supplied over-budget selections are preserved and marked invalid. New choices remain
|
|
38
|
+
blocked until the state is valid, while deselection and point increases are still available.
|
|
39
|
+
Unknown, duplicate, or inactive supplied IDs are removed after data loads.
|
|
40
|
+
|
|
41
|
+
## Lit API
|
|
42
|
+
|
|
43
|
+
```ts
|
|
44
|
+
import type {
|
|
45
|
+
FellowshipTalentCalculatorElement,
|
|
46
|
+
TalentSelectionChangeEvent,
|
|
47
|
+
} from "@crockery/fellowship-components/talent-calculator";
|
|
48
|
+
|
|
49
|
+
const calculator = document.querySelector<FellowshipTalentCalculatorElement>(
|
|
50
|
+
"fellowship-talent-calculator",
|
|
51
|
+
)!;
|
|
52
|
+
|
|
53
|
+
calculator.selectedTalentIds = ["firemage-talent-id"];
|
|
54
|
+
calculator.addEventListener(
|
|
55
|
+
"fellowship-talent-selection-change",
|
|
56
|
+
(event: TalentSelectionChangeEvent) => {
|
|
57
|
+
console.log(event.detail.selectedTalentIds, event.detail.remainingPoints);
|
|
58
|
+
},
|
|
59
|
+
);
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### Custom talent content
|
|
63
|
+
|
|
64
|
+
The element owns each card button, talent image, point cost, tooltip, positioning, focus, and
|
|
65
|
+
selection behavior. A renderer changes the label presentation inside that shell and must not
|
|
66
|
+
return nested interactive controls. The default talent image remains visible; hide or restyle it
|
|
67
|
+
with `::part(talent-icon)` when supplying a complete custom presentation:
|
|
68
|
+
|
|
69
|
+
```ts
|
|
70
|
+
import { html } from "lit";
|
|
71
|
+
|
|
72
|
+
calculator.renderTalent = ({ talent, selected }) => html`
|
|
73
|
+
<span class=${selected ? "chosen" : "available"}>
|
|
74
|
+
${talent.row}.${talent.column} · ${talent.talent.name}
|
|
75
|
+
</span>
|
|
76
|
+
`;
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## React
|
|
80
|
+
|
|
81
|
+
```tsx
|
|
82
|
+
import { useState } from "react";
|
|
83
|
+
import { TalentCalculator } from "@crockery/fellowship-components/react/talent-calculator";
|
|
84
|
+
|
|
85
|
+
export function HeroBuild() {
|
|
86
|
+
const [selected, setSelected] = useState<readonly string[]>([]);
|
|
87
|
+
|
|
88
|
+
return (
|
|
89
|
+
<TalentCalculator
|
|
90
|
+
heroId="firemage"
|
|
91
|
+
talentPoints={14}
|
|
92
|
+
selectedTalentIds={selected}
|
|
93
|
+
onSelectionChange={(event) => setSelected(event.detail.selectedTalentIds)}
|
|
94
|
+
renderTalent={({ talent }) => <span>{talent.talent.name}</span>}
|
|
95
|
+
/>
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
React talent renderers mount presentation-only nodes into stable named slots. The web component
|
|
101
|
+
continues to own button semantics and selection state.
|
|
102
|
+
|
|
103
|
+
## Data and assets
|
|
104
|
+
|
|
105
|
+
Self-host images with `assetBaseUrl`, or replace the complete provider:
|
|
106
|
+
|
|
107
|
+
```ts
|
|
108
|
+
import type { FellowshipTalentDataSource } from "@crockery/fellowship-components/talent-calculator";
|
|
109
|
+
|
|
110
|
+
declare const applicationTalentData: FellowshipTalentDataSource;
|
|
111
|
+
|
|
112
|
+
calculator.assetBaseUrl = "https://static.example.com/fellowship-data/v1.0.0/";
|
|
113
|
+
|
|
114
|
+
const source: FellowshipTalentDataSource = {
|
|
115
|
+
async loadHeroTalents(heroId, signal) {
|
|
116
|
+
return applicationTalentData.loadHeroTalents(heroId, signal);
|
|
117
|
+
},
|
|
118
|
+
resolveAssetUrl(asset) {
|
|
119
|
+
return new URL(asset.path, "https://static.example.com/").href;
|
|
120
|
+
},
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
calculator.dataSource = source;
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## API
|
|
127
|
+
|
|
128
|
+
### Properties
|
|
129
|
+
|
|
130
|
+
- `heroId: string`
|
|
131
|
+
- `talentPoints: number` — defaults to `14`, normalized from `0` through
|
|
132
|
+
`MAX_TALENT_POINTS`.
|
|
133
|
+
- `selectedTalentIds: readonly string[]`
|
|
134
|
+
- `assetBaseUrl: string | null`
|
|
135
|
+
- `dataSource: FellowshipTalentDataSource | null`
|
|
136
|
+
- `renderTalent: LitTalentRenderer | null`
|
|
137
|
+
- `showPointControls: boolean` — defaults to `true`.
|
|
138
|
+
- `showTooltips: boolean` — defaults to `true`.
|
|
139
|
+
|
|
140
|
+
### Methods
|
|
141
|
+
|
|
142
|
+
- `selectTalent(talentId): boolean`
|
|
143
|
+
- `deselectTalent(talentId): boolean`
|
|
144
|
+
- `reset()` — clears selections without changing the point total.
|
|
145
|
+
- `retry()`
|
|
146
|
+
|
|
147
|
+
### Events
|
|
148
|
+
|
|
149
|
+
- `fellowship-talents-ready`
|
|
150
|
+
- `fellowship-talents-error`
|
|
151
|
+
- `fellowship-talents-warning`
|
|
152
|
+
- `fellowship-talent-selection-change`
|
|
153
|
+
- `fellowship-talent-points-change`
|
|
154
|
+
|
|
155
|
+
All events bubble across the shadow boundary and carry typed details. Selection events report
|
|
156
|
+
`selectedTalentIds`, `spentPoints`, `remainingPoints`, and their change source.
|
|
157
|
+
|
|
158
|
+
## Accessibility
|
|
159
|
+
|
|
160
|
+
Rows are semantic fieldsets with legends. Cards are native buttons with `aria-pressed`, accessible
|
|
161
|
+
descriptions, visible focus rings, and focusable `aria-disabled` states. Clear All and the point
|
|
162
|
+
controls are native buttons with visible focus states and 44px interaction targets. Status changes
|
|
163
|
+
use polite live regions; newly raised errors use assertive announcements.
|
|
164
|
+
|
|
165
|
+
## Theming
|
|
166
|
+
|
|
167
|
+
The calculator inherits the shared `--fellowship-field-*` theme described in the package README.
|
|
168
|
+
Its existing component tokens take precedence, so established themes remain compatible.
|
|
169
|
+
|
|
170
|
+
```css
|
|
171
|
+
fellowship-talent-calculator {
|
|
172
|
+
--fellowship-talent-background: #0b0f13;
|
|
173
|
+
--fellowship-talent-surface: #10161c;
|
|
174
|
+
--fellowship-talent-card-background: #10161c;
|
|
175
|
+
--fellowship-talent-selection: #69c4d4;
|
|
176
|
+
--fellowship-talent-points: #c1a36a;
|
|
177
|
+
--fellowship-talent-danger: #d9784c;
|
|
178
|
+
--fellowship-talent-icon-size: 56px;
|
|
179
|
+
}
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
CSS parts: `container`, `header`, `heading`, `points`, `points-spent`, `points-total`,
|
|
183
|
+
`point-controls`, `point-control`, `clear-all`, `grid`, `row`, `row-label`, `talent`,
|
|
184
|
+
`talent-icon`, `talent-icon-fallback`, `talent-name`, `talent-cost`, `status`,
|
|
185
|
+
`empty-state`, and `error-state`.
|
|
186
|
+
|
|
187
|
+
Design tokens use the `--fellowship-talent-*` prefix and cover the background and surfaces,
|
|
188
|
+
foreground hierarchy, disabled state, border, selection, focus, point accent, danger state,
|
|
189
|
+
control surface, icon size, interface and tactical font families, and radius. The compact
|
|
190
|
+
single-column layout is activated by the component's own container width rather than the viewport.
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# Tooltip
|
|
2
|
+
|
|
3
|
+
`<fellowship-tooltip>` provides accessible, collision-aware Fellowship tooltips with a safe
|
|
4
|
+
rich-text resolver.
|
|
5
|
+
|
|
6
|
+
## Import and basic usage
|
|
7
|
+
|
|
8
|
+
```ts
|
|
9
|
+
import "@crockery/fellowship-components/tooltip";
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
```html
|
|
13
|
+
<fellowship-tooltip
|
|
14
|
+
heading="Runic Convergence"
|
|
15
|
+
text="Gain <rt.absorb>20 absorb</> and restore <rt.mana>5 mana</>."
|
|
16
|
+
>
|
|
17
|
+
<button type="button">Inspect talent</button>
|
|
18
|
+
</fellowship-tooltip>
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
The default slot contains the trigger. Supply fully custom tooltip content through the named
|
|
22
|
+
`content` slot.
|
|
23
|
+
|
|
24
|
+
## React
|
|
25
|
+
|
|
26
|
+
```tsx
|
|
27
|
+
import { Tooltip } from "@crockery/fellowship-components/react/tooltip";
|
|
28
|
+
|
|
29
|
+
export function Help() {
|
|
30
|
+
return (
|
|
31
|
+
<Tooltip heading="Talent points" text="Points can be reassigned at any time.">
|
|
32
|
+
<button type="button">Help</button>
|
|
33
|
+
</Tooltip>
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Pass a React node to the wrapper's `content` prop to fill the named content slot.
|
|
39
|
+
|
|
40
|
+
## Interaction and positioning
|
|
41
|
+
|
|
42
|
+
- Pointer hover opens after `openDelay`, which defaults to `250` milliseconds.
|
|
43
|
+
- Keyboard focus opens immediately.
|
|
44
|
+
- Pointer leave, focus leaving the host, or Escape closes the tooltip.
|
|
45
|
+
- `placement` accepts `auto`, `top`, `right`, `bottom`, or `left`.
|
|
46
|
+
- Automatic placement prefers top, then bottom, right, and left.
|
|
47
|
+
- The surface repositions on scroll, viewport resize, trigger resize, and content resize.
|
|
48
|
+
- Positioning maintains an 8px trigger gap and viewport collision margin.
|
|
49
|
+
- The native Popover API is used when available, with a fixed-position fallback.
|
|
50
|
+
|
|
51
|
+
Tooltip content is noninteractive and uses `role="tooltip"`. The first slotted trigger receives a
|
|
52
|
+
plain-text `aria-description` while connected; any existing value is restored afterward.
|
|
53
|
+
|
|
54
|
+
## API
|
|
55
|
+
|
|
56
|
+
### Properties
|
|
57
|
+
|
|
58
|
+
- `heading: string | null`
|
|
59
|
+
- `text: string | null`
|
|
60
|
+
- `placement: "auto" | "top" | "right" | "bottom" | "left"`
|
|
61
|
+
- `openDelay: number`
|
|
62
|
+
- `disabled: boolean`
|
|
63
|
+
|
|
64
|
+
### Methods
|
|
65
|
+
|
|
66
|
+
- `show()`
|
|
67
|
+
- `hide()`
|
|
68
|
+
|
|
69
|
+
### Event
|
|
70
|
+
|
|
71
|
+
`fellowship-tooltip-toggle` bubbles across the shadow boundary. Its detail contains `open` and
|
|
72
|
+
the reason: `pointer`, `focus`, `escape`, `programmatic`, or `disabled`.
|
|
73
|
+
|
|
74
|
+
## Fellowship rich text
|
|
75
|
+
|
|
76
|
+
Descriptions are parsed into inert Lit text bindings. The component never uses `unsafeHTML` or
|
|
77
|
+
executes arbitrary markup.
|
|
78
|
+
|
|
79
|
+
Supported tags:
|
|
80
|
+
|
|
81
|
+
- `bold` and `rt.bold`
|
|
82
|
+
- `rt.absorb`
|
|
83
|
+
- `rt.warning`
|
|
84
|
+
- `rt.effect`
|
|
85
|
+
- `rt.mana`
|
|
86
|
+
- `rt.heal`
|
|
87
|
+
- `rt.meikoability1`
|
|
88
|
+
- `rt.meikoability2`
|
|
89
|
+
|
|
90
|
+
Both `</>` and named closing tags are supported. Unknown tags keep their text without styling,
|
|
91
|
+
and malformed or unclosed markup recovers safely. Paragraphs and line breaks are preserved.
|
|
92
|
+
|
|
93
|
+
The entry point also exports:
|
|
94
|
+
|
|
95
|
+
```ts
|
|
96
|
+
import {
|
|
97
|
+
resolveFellowshipRichText,
|
|
98
|
+
stripFellowshipRichText,
|
|
99
|
+
} from "@crockery/fellowship-components/tooltip";
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## Theming
|
|
103
|
+
|
|
104
|
+
The tooltip inherits the shared `--fellowship-field-*` theme and exposes the established
|
|
105
|
+
`--fellowship-tooltip-*` overrides. Its desaturated semantic defaults are ability `#76C5D2`,
|
|
106
|
+
warning `#D4A761`, effect `#AFA1CA`, resource `#7CB2C2`, healing `#82B997`, Meiko primary
|
|
107
|
+
`#C892A0`, and Meiko secondary `#8DBAB7`.
|
|
108
|
+
|
|
109
|
+
CSS parts: `trigger`, `surface`, `arrow`, `heading`, `content`, and `paragraph`.
|
|
110
|
+
|
|
111
|
+
Design tokens use the `--fellowship-tooltip-*` prefix and cover surface, foreground, muted text,
|
|
112
|
+
border, focus, semantic rich-text tones, font family, radius, and maximum width. Reduced-motion
|
|
113
|
+
preferences remove translated transitions.
|