which-heading-do-i-need 0.0.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.md ADDED
@@ -0,0 +1,9 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2025
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6
+
7
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8
+
9
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,12 @@
1
+ # which-heading-do-i-need
2
+
3
+ Don't want to think about which `<h1>` - `<h6>` to use? This library will synchronously calculate your heading for you.
4
+
5
+ Docs:
6
+ https://ember-primitives.pages.dev/6-utils/get-section-heading-level.md
7
+
8
+ ## Installation
9
+
10
+ ```
11
+ npm add which-heading-do-i-need
12
+ ```
@@ -0,0 +1,2 @@
1
+ export declare function getSectionHeadingLevel(node: Text): number;
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AA2IA,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,IAAI,UAWhD"}
@@ -0,0 +1 @@
1
+ //# sourceMappingURL=template-registry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"template-registry.d.ts","sourceRoot":"","sources":["../src/template-registry.ts"],"names":[],"mappings":""}
package/dist/index.js ADDED
@@ -0,0 +1,100 @@
1
+ const LOOKUP = new WeakMap();
2
+ const BOUNDARY_ELEMENTS = new Set(['SECTION', 'ARTICLE', 'ASIDE', 'HEADER', 'FOOTER', 'MAIN', 'NAV']);
3
+
4
+ /**
5
+ * A set with both cases is more performant than calling toLowerCase
6
+ */
7
+ const SECTION_HEADINGS = new Set(['h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6']);
8
+ const TEST_BOUNDARY = 'ember-testing';
9
+ function isRoot(element) {
10
+ return element === document.body || element.id === TEST_BOUNDARY;
11
+ }
12
+ function findHeadingIn(node) {
13
+ if (!(node instanceof Element)) return;
14
+ if (SECTION_HEADINGS.has(node.tagName)) {
15
+ const level = parseInt(node.tagName.replace('h', '').replace('H', ''));
16
+ return level;
17
+ }
18
+ for (const child of node.children) {
19
+ const level = findHeadingIn(child);
20
+ if (level) return level;
21
+ }
22
+ }
23
+
24
+ /**
25
+ * The Platform native 'closest' function can't punch through shadow-boundaries
26
+ */
27
+ function nearestAncestor(node, matcher) {
28
+ let parent = node.parentElement;
29
+ if (!parent) return;
30
+ while (parent) {
31
+ if (parent instanceof Element) {
32
+ if (matcher(parent)) return parent;
33
+ }
34
+ if (parent instanceof ShadowRoot) {
35
+ parent = parent.host;
36
+ }
37
+ parent = parent.parentNode;
38
+ }
39
+ }
40
+
41
+ /**
42
+ * The algorithm:
43
+ *
44
+ * section <- "our" level-changing boundary element
45
+ * h# <- the element we want to figure out the level of
46
+ *
47
+ * We start assuming we'll emit an h1.
48
+ * We adjust this based on what we find crawling up the tree.
49
+ *
50
+ * While traversing up, when we go from the h# to the section,
51
+ * and ignore it. Because this alone has no bearing on if the h# should be an h2.
52
+ * We need to continue traversing upwards, until we hit the next boundary element.
53
+ *
54
+ * IF we would change the level the heading, we will find another heading between
55
+ * these two boundary elements.
56
+ * We'll need to check the subtrees between these elements, stopping if we
57
+ * encounter other boundary elements.
58
+ *
59
+ */
60
+ function levelOf(node) {
61
+ const ourBoundary = nearestAncestor(node, el => BOUNDARY_ELEMENTS.has(el.tagName));
62
+
63
+ /**
64
+ * We are the top-level
65
+ */
66
+ if (!ourBoundary) {
67
+ return 1;
68
+ }
69
+ const stopAt = nearestAncestor(ourBoundary, el => {
70
+ if (BOUNDARY_ELEMENTS.has(el.tagName)) return true;
71
+ return isRoot(el);
72
+ });
73
+ if (!stopAt) {
74
+ throw new Error(`[BUG]: Could not find a stopping boundary for automatic heading level detection. Checked for ${[...BOUNDARY_ELEMENTS, 'body', '#ember-testing'].map(x => x.toLowerCase()).join(', ')}`);
75
+ }
76
+ let current = ourBoundary.parentNode;
77
+ while (current) {
78
+ const level = findHeadingIn(current);
79
+ if (level) {
80
+ return level + 1;
81
+ }
82
+ if (current === stopAt) break;
83
+ if (current instanceof ShadowRoot) {
84
+ current = current.host;
85
+ }
86
+ current = current.parentNode;
87
+ }
88
+ return 1;
89
+ }
90
+ function getSectionHeadingLevel(node) {
91
+ const existing = LOOKUP.get(node);
92
+ if (existing) return existing;
93
+ const parentLevel = levelOf(node);
94
+ const myLevel = parentLevel;
95
+ LOOKUP.set(node, myLevel);
96
+ return myLevel;
97
+ }
98
+
99
+ export { getSectionHeadingLevel };
100
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sources":["../src/index.ts"],"sourcesContent":["const LOOKUP = new WeakMap<Text, number>();\n\nconst BOUNDARY_ELEMENTS = new Set([\n 'SECTION',\n 'ARTICLE',\n 'ASIDE',\n 'HEADER',\n 'FOOTER',\n 'MAIN',\n 'NAV',\n]);\n\n/**\n * A set with both cases is more performant than calling toLowerCase\n */\nconst SECTION_HEADINGS = new Set([\n 'h1',\n 'h2',\n 'h3',\n 'h4',\n 'h5',\n 'h6',\n 'H1',\n 'H2',\n 'H3',\n 'H4',\n 'H5',\n 'H6',\n]);\nconst TEST_BOUNDARY = 'ember-testing';\n\nfunction isRoot(element: Element) {\n return element === document.body || element.id === TEST_BOUNDARY;\n}\n\nfunction findHeadingIn(node: ParentNode): number | undefined {\n if (!(node instanceof Element)) return;\n\n if (SECTION_HEADINGS.has(node.tagName)) {\n const level = parseInt(node.tagName.replace('h', '').replace('H', ''));\n\n return level;\n }\n\n for (const child of node.children) {\n const level = findHeadingIn(child);\n\n if (level) return level;\n }\n}\n\n/**\n * The Platform native 'closest' function can't punch through shadow-boundaries\n */\nfunction nearestAncestor(\n node: Text | Element,\n matcher: (el: Element) => boolean,\n) {\n let parent: ParentNode | null = node.parentElement;\n\n if (!parent) return;\n\n while (parent) {\n if (parent instanceof Element) {\n if (matcher(parent)) return parent;\n }\n\n if (parent instanceof ShadowRoot) {\n parent = parent.host;\n }\n\n parent = parent.parentNode;\n }\n}\n\n/**\n * The algorithm:\n *\n * section <- \"our\" level-changing boundary element\n * h# <- the element we want to figure out the level of\n *\n * We start assuming we'll emit an h1.\n * We adjust this based on what we find crawling up the tree.\n *\n * While traversing up, when we go from the h# to the section,\n * and ignore it. Because this alone has no bearing on if the h# should be an h2.\n * We need to continue traversing upwards, until we hit the next boundary element.\n *\n * IF we would change the level the heading, we will find another heading between\n * these two boundary elements.\n * We'll need to check the subtrees between these elements, stopping if we\n * encounter other boundary elements.\n *\n */\nfunction levelOf(node: Text): number {\n const ourBoundary = nearestAncestor(node, (el) =>\n BOUNDARY_ELEMENTS.has(el.tagName),\n );\n\n /**\n * We are the top-level\n */\n if (!ourBoundary) {\n return 1;\n }\n\n const stopAt = nearestAncestor(ourBoundary, (el) => {\n if (BOUNDARY_ELEMENTS.has(el.tagName)) return true;\n\n return isRoot(el);\n });\n\n if (!stopAt) {\n throw new Error(\n `[BUG]: Could not find a stopping boundary for automatic heading level detection. Checked for ${[...BOUNDARY_ELEMENTS, 'body', '#ember-testing'].map((x) => x.toLowerCase()).join(', ')}`,\n );\n }\n\n let current: ParentNode | null = ourBoundary.parentNode;\n\n while (current) {\n const level = findHeadingIn(current);\n\n if (level) {\n return level + 1;\n }\n\n if (current === stopAt) break;\n\n if (current instanceof ShadowRoot) {\n current = current.host;\n }\n\n current = current.parentNode;\n }\n\n return 1;\n}\n\nexport function getSectionHeadingLevel(node: Text) {\n const existing = LOOKUP.get(node);\n\n if (existing) return existing;\n\n const parentLevel = levelOf(node);\n const myLevel = parentLevel;\n\n LOOKUP.set(node, myLevel);\n\n return myLevel;\n}\n"],"names":["LOOKUP","WeakMap","BOUNDARY_ELEMENTS","Set","SECTION_HEADINGS","TEST_BOUNDARY","isRoot","element","document","body","id","findHeadingIn","node","Element","has","tagName","level","parseInt","replace","child","children","nearestAncestor","matcher","parent","parentElement","ShadowRoot","host","parentNode","levelOf","ourBoundary","el","stopAt","Error","map","x","toLowerCase","join","current","getSectionHeadingLevel","existing","get","parentLevel","myLevel","set"],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,51 @@
1
+ {
2
+ "name": "which-heading-do-i-need",
3
+ "version": "0.0.0",
4
+ "description": "The default blueprint for Embroider v2 addons.",
5
+ "type": "module",
6
+ "keywords": [
7
+ "html",
8
+ "section",
9
+ "heading",
10
+ "section-heading",
11
+ "a11y",
12
+ "headless"
13
+ ],
14
+ "repository": "https://github.com/universal-ember/ember-primitives",
15
+ "license": "MIT",
16
+ "author": "NullVoxPopuli",
17
+ "exports": {
18
+ ".": {
19
+ "types": "./declarations/index.d.ts",
20
+ "default": "./dist/index.js"
21
+ }
22
+ },
23
+ "files": [
24
+ "declarations",
25
+ "dist"
26
+ ],
27
+ "devDependencies": {
28
+ "@babel/core": "^7.25.2",
29
+ "@babel/plugin-transform-typescript": "^7.25.2",
30
+ "@babel/runtime": "^7.25.6",
31
+ "@ember/library-tsconfig": "^1.0.0",
32
+ "@embroider/addon-dev": "^8.1.0",
33
+ "@nullvoxpopuli/eslint-configs": "^5.3.4",
34
+ "@rollup/plugin-babel": "^6.0.4",
35
+ "concurrently": "^9.0.1",
36
+ "eslint": "^9.17.0",
37
+ "prettier": "^3.4.2",
38
+ "rollup": "^4.22.5",
39
+ "typescript": "~5.8.3"
40
+ },
41
+ "scripts": {
42
+ "build": "rollup --config",
43
+ "format": "prettier . --cache --write",
44
+ "lint": "concurrently \"pnpm:lint:*(!fix)\" --names \"lint:\" --prefixColors auto",
45
+ "lint:fix": "concurrently \"pnpm:lint:*:fix\" --names \"fix:\" --prefixColors auto && pnpm run format",
46
+ "lint:format": "prettier . --check",
47
+ "lint:js": "eslint .",
48
+ "lint:js:fix": "eslint . --fix",
49
+ "lint:types": "tsc --noEmit"
50
+ }
51
+ }